From 0e5f56b562fbc9e483e2f89364f2db55001f76f0 Mon Sep 17 00:00:00 2001 From: warmuptill <19472752+WarmUpTill@users.noreply.github.com> Date: Tue, 13 May 2025 08:59:21 +0200 Subject: [PATCH] Add tesseract config file support --- data/locale/de-DE.ini | 12 +- data/locale/en-US.ini | 30 +- data/locale/es-ES.ini | 12 +- data/locale/fr-FR.ini | 16 +- data/locale/ja-JP.ini | 18 +- data/locale/pt-BR.ini | 20 +- data/locale/tr-TR.ini | 8 +- data/locale/zh-CN.ini | 20 +- plugins/video/macro-condition-video.cpp | 394 +++++++++++++----------- plugins/video/macro-condition-video.hpp | 17 +- plugins/video/opencv-helpers.cpp | 11 +- plugins/video/opencv-helpers.hpp | 19 +- plugins/video/parameter-wrappers.cpp | 77 +++-- plugins/video/parameter-wrappers.hpp | 6 + plugins/video/preview-dialog.cpp | 8 +- 15 files changed, 384 insertions(+), 284 deletions(-) diff --git a/data/locale/de-DE.ini b/data/locale/de-DE.ini index 9751fcee..1272a5a9 100644 --- a/data/locale/de-DE.ini +++ b/data/locale/de-DE.ini @@ -194,12 +194,12 @@ AdvSceneSwitcher.condition.video.modelLoadFail="Modelldaten konnten nicht gelade AdvSceneSwitcher.condition.video.type.main="OBS's Haupt-Ausgabe" AdvSceneSwitcher.condition.video.type.source="Quelle" AdvSceneSwitcher.condition.video.type.scene="Szene" -AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" -AdvSceneSwitcher.condition.video.entry.modelPath="Modelldaten (Haar-Kaskaden-Klassifikator): {{modelDataPath}}" -AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimale Anzahl von Nachbarn: {{minNeighbors}}" -AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduzieren Sie die CPU-Belastung, indem Sie die Prüfung nur alle {{throttleCount}} Millisekunden" -AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Kontrolle nur im Bereich durchführen" -AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" +AdvSceneSwitcher.condition.video.layout="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" +AdvSceneSwitcher.condition.video.layout.modelPath="Modelldaten (Haar-Kaskaden-Klassifikator): {{modelDataPath}}" +AdvSceneSwitcher.condition.video.layout.minNeighbor="Minimale Anzahl von Nachbarn: {{minNeighbors}}" +AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduzieren Sie die CPU-Belastung, indem Sie die Prüfung nur alle {{throttleCount}} Millisekunden" +AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Kontrolle nur im Bereich durchführen" +AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" AdvSceneSwitcher.condition.video.minSize="Minimale Größe:" AdvSceneSwitcher.condition.video.maxSize="Maximale Größe:" AdvSceneSwitcher.condition.video.selectArea="Bereich auswählen" diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index e54aa0c8..ecae944c 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -392,6 +392,13 @@ AdvSceneSwitcher.condition.video.patternMatchSuccessFullImage="Full image matche AdvSceneSwitcher.condition.video.objectMatchFail="Object was not found!" AdvSceneSwitcher.condition.video.objectMatchSuccess="Object is highlighted in red" AdvSceneSwitcher.condition.video.ocrMatchSuccess="Detected text:\n\n%1" +AdvSceneSwitcher.condition.video.ocrMatchFail="Could not detect any text!" +AdvSceneSwitcher.condition.video.ocrUseConfigFile="Use custom tesseract config file" +AdvSceneSwitcher.condition.video.ocrOpenConfigFile="Open" +AdvSceneSwitcher.condition.video.ocrOpenConfig.createFailed="Could not create the config file!" +AdvSceneSwitcher.condition.video.ocrOpenConfig.openFailed="Could not open the config file!" +AdvSceneSwitcher.condition.video.ocrConfigReload="Reload configuration file" +AdvSceneSwitcher.condition.video.ocrConfigHint="Tesseract config files consist of lines with parameter-value pairs (space separated).\nFor example:\n\ntessedit_char_blacklist\t\t\t\t\"abc\"\nlanguage_model_penalty_non_dict_word\t0" AdvSceneSwitcher.condition.video.modelLoadFail="Model data could not be loaded!" AdvSceneSwitcher.condition.video.selectColor="Select Color" AdvSceneSwitcher.condition.video.ocrMode.singleColumn="Single column of text of variable sizes" @@ -411,17 +418,18 @@ AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription="How similar AdvSceneSwitcher.condition.video.type.main="OBS's main output" AdvSceneSwitcher.condition.video.type.source="Source" AdvSceneSwitcher.condition.video.type.scene="Scene" -AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" -AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier):{{modelDataPath}}" -AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors:{{minNeighbors}}" -AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU load by performing check only every{{throttleCount}}milliseconds" -AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Perform check only in area" -AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" -AdvSceneSwitcher.condition.video.entry.orcColorPick="Check for text color:{{textColor}}{{selectColor}}" -AdvSceneSwitcher.condition.video.entry.orcTextType="Check for text type:{{textType}}" -AdvSceneSwitcher.condition.video.entry.orcBaseDir="Tesseract base directory:{{tesseractBaseDir}}" -AdvSceneSwitcher.condition.video.entry.orcLanguage="Check for language:{{languageCode}}" -AdvSceneSwitcher.condition.video.entry.color="Check for color:{{color}}{{selectColor}}" +AdvSceneSwitcher.condition.video.layout="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" +AdvSceneSwitcher.condition.video.layout.modelPath="Model data (haar cascade classifier):{{modelDataPath}}" +AdvSceneSwitcher.condition.video.layout.minNeighbor="Minimum neighbors:{{minNeighbors}}" +AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduce CPU load by performing check only every{{throttleCount}}milliseconds" +AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Perform check only in area" +AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" +AdvSceneSwitcher.condition.video.layout.ocrColorPick="Check for text color:{{textColor}}{{selectColor}}" +AdvSceneSwitcher.condition.video.layout.ocrTextType="Check for text type:{{textType}}" +AdvSceneSwitcher.condition.video.layout.ocrBaseDir="Tesseract base directory:{{tesseractBaseDir}}" +AdvSceneSwitcher.condition.video.layout.ocrLanguage="Check for language:{{languageCode}}" +AdvSceneSwitcher.condition.video.layout.ocrConfig="Config file:{{configFile}}{{openConfigFile}}{{reloadConfig}}{{configFileHint}}" +AdvSceneSwitcher.condition.video.layout.color="Check for color:{{color}}{{selectColor}}" AdvSceneSwitcher.condition.video.minSize="Minimum size:" AdvSceneSwitcher.condition.video.maxSize="Maximum size:" AdvSceneSwitcher.condition.video.selectArea="Select area" diff --git a/data/locale/es-ES.ini b/data/locale/es-ES.ini index 929b33b7..900efdf4 100644 --- a/data/locale/es-ES.ini +++ b/data/locale/es-ES.ini @@ -154,12 +154,12 @@ AdvSceneSwitcher.condition.video.patternMatchSuccess="El patrón está resaltado AdvSceneSwitcher.condition.video.objectMatchFail="¡No se encontró el objeto!" AdvSceneSwitcher.condition.video.objectMatchSuccess="El objeto está resaltado en rojo" AdvSceneSwitcher.condition.video.modelLoadFail="¡No se pudieron cargar los datos del modelo!" -AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" -AdvSceneSwitcher.condition.video.entry.modelPath="Datos del modelo (haar cascade classifier): {{modelDataPath}}" -AdvSceneSwitcher.condition.video.entry.minNeighbor="Mínimo de vecinos: {{minNeighbors}}" -AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduzca la carga de la CPU realizando una comprobación solo cada {{throttleCount}} milisegundos" -AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Realizar comprobación solo en el área" -AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}Realizar comprobación solo en el área {{checkArea}} {{selectArea}}" +AdvSceneSwitcher.condition.video.layout="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" +AdvSceneSwitcher.condition.video.layout.modelPath="Datos del modelo (haar cascade classifier): {{modelDataPath}}" +AdvSceneSwitcher.condition.video.layout.minNeighbor="Mínimo de vecinos: {{minNeighbors}}" +AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduzca la carga de la CPU realizando una comprobación solo cada {{throttleCount}} milisegundos" +AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Realizar comprobación solo en el área" +AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}Realizar comprobación solo en el área {{checkArea}} {{selectArea}}" AdvSceneSwitcher.condition.video.minSize="Tamaño mínimo:" AdvSceneSwitcher.condition.video.maxSize="Tamaño máximo:" AdvSceneSwitcher.condition.video.selectArea="Seleccionar área" diff --git a/data/locale/fr-FR.ini b/data/locale/fr-FR.ini index b8cfd337..7e7e2337 100644 --- a/data/locale/fr-FR.ini +++ b/data/locale/fr-FR.ini @@ -257,14 +257,14 @@ AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription="À quel poi AdvSceneSwitcher.condition.video.type.main="Sortie principale d'OBS" AdvSceneSwitcher.condition.video.type.source="Source" AdvSceneSwitcher.condition.video.type.scene="Scène" -AdvSceneSwitcher.condition.video.entry.modelPath="Données du modèle (classificateur de cascade de Haar) :{{modelDataPath}}" -AdvSceneSwitcher.condition.video.entry.minNeighbor="Nombre minimum de voisins :{{minNeighbors}}" -AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Réduire la charge CPU en effectuant la vérification uniquement toutes les{{throttleCount}}millisecondes" -AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Effectuer la vérification uniquement dans la zone" -AdvSceneSwitcher.condition.video.entry.orcColorPick="Vérifier la couleur du texte :{{textColor}}{{selectColor}}" -AdvSceneSwitcher.condition.video.entry.orcTextType="Vérifier le type de texte :{{textType}}" -AdvSceneSwitcher.condition.video.entry.orcLanguage="Vérifier la langue :{{languageCode}}" -AdvSceneSwitcher.condition.video.entry.color="Vérifier la couleur :{{color}}{{selectColor}}" +AdvSceneSwitcher.condition.video.layout.modelPath="Données du modèle (classificateur de cascade de Haar) :{{modelDataPath}}" +AdvSceneSwitcher.condition.video.layout.minNeighbor="Nombre minimum de voisins :{{minNeighbors}}" +AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Réduire la charge CPU en effectuant la vérification uniquement toutes les{{throttleCount}}millisecondes" +AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Effectuer la vérification uniquement dans la zone" +AdvSceneSwitcher.condition.video.layout.ocrColorPick="Vérifier la couleur du texte :{{textColor}}{{selectColor}}" +AdvSceneSwitcher.condition.video.layout.ocrTextType="Vérifier le type de texte :{{textType}}" +AdvSceneSwitcher.condition.video.layout.ocrLanguage="Vérifier la langue :{{languageCode}}" +AdvSceneSwitcher.condition.video.layout.color="Vérifier la couleur :{{color}}{{selectColor}}" AdvSceneSwitcher.condition.video.minSize="Taille minimale :" AdvSceneSwitcher.condition.video.maxSize="Taille maximale :" AdvSceneSwitcher.condition.video.selectArea="Sélectionner la zone" diff --git a/data/locale/ja-JP.ini b/data/locale/ja-JP.ini index 975c8271..837ce0a2 100644 --- a/data/locale/ja-JP.ini +++ b/data/locale/ja-JP.ini @@ -379,16 +379,14 @@ AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription="一致と AdvSceneSwitcher.condition.video.type.main="OBS's main output" AdvSceneSwitcher.condition.video.type.source="ソース" AdvSceneSwitcher.condition.video.type.scene="シーン" -; AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" -AdvSceneSwitcher.condition.video.entry.modelPath="モデルデータ (そのカスケード分類子):{{modelDataPath}}" -AdvSceneSwitcher.condition.video.entry.minNeighbor="最小近傍数:{{minNeighbors}}" -AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}:{{throttleCount}}ミリ秒ごとにのみチェックを実行することでCPU負荷を軽減します" -AdvSceneSwitcher.condition.video.entry.checkAreaEnable="エリア内のみチェックを実施" -; AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" -AdvSceneSwitcher.condition.video.entry.orcColorPick="テキストの色の確認:{{textColor}}{{selectColor}}" -AdvSceneSwitcher.condition.video.entry.orcTextType="テキストタイプの確認:{{textType}}" -AdvSceneSwitcher.condition.video.entry.orcLanguage="言語の確認:{{languageCode}}" -AdvSceneSwitcher.condition.video.entry.color="カラーを確認してください:{{color}}{{selectColor}}" +AdvSceneSwitcher.condition.video.layout.modelPath="モデルデータ (そのカスケード分類子):{{modelDataPath}}" +AdvSceneSwitcher.condition.video.layout.minNeighbor="最小近傍数:{{minNeighbors}}" +AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}:{{throttleCount}}ミリ秒ごとにのみチェックを実行することでCPU負荷を軽減します" +AdvSceneSwitcher.condition.video.layout.checkAreaEnable="エリア内のみチェックを実施" +AdvSceneSwitcher.condition.video.layout.ocrColorPick="テキストの色の確認:{{textColor}}{{selectColor}}" +AdvSceneSwitcher.condition.video.layout.ocrTextType="テキストタイプの確認:{{textType}}" +AdvSceneSwitcher.condition.video.layout.ocrLanguage="言語の確認:{{languageCode}}" +AdvSceneSwitcher.condition.video.layout.color="カラーを確認してください:{{color}}{{selectColor}}" AdvSceneSwitcher.condition.video.minSize="最小サイズ:" AdvSceneSwitcher.condition.video.maxSize="最大サイズ:" AdvSceneSwitcher.condition.video.selectArea="範囲指定" diff --git a/data/locale/pt-BR.ini b/data/locale/pt-BR.ini index a532ecbd..95587aec 100644 --- a/data/locale/pt-BR.ini +++ b/data/locale/pt-BR.ini @@ -372,16 +372,16 @@ AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription="Quão semel AdvSceneSwitcher.condition.video.type.main="Saída principal do OBS" AdvSceneSwitcher.condition.video.type.source="Fonte" AdvSceneSwitcher.condition.video.type.scene="Cena" -AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" -AdvSceneSwitcher.condition.video.entry.modelPath="Dados do modelo (classificador em cascata haar):{{modelDataPath}}" -AdvSceneSwitcher.condition.video.entry.minNeighbor="Vizinhos mínimos:{{minNeighbors}}" -AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduzir carga de CPU realizando a verificação apenas a cada {{throttleCount}} milissegundos" -AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Realizar verificação apenas na área" -AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" -AdvSceneSwitcher.condition.video.entry.orcColorPick="Verificar cor do texto:{{textColor}}{{selectColor}}" -AdvSceneSwitcher.condition.video.entry.orcTextType="Verificar tipo de texto:{{textType}}" -AdvSceneSwitcher.condition.video.entry.orcLanguage="Verificar idioma:{{languageCode}}" -AdvSceneSwitcher.condition.video.entry.color="Verificar cor:{{color}}{{selectColor}}" +AdvSceneSwitcher.condition.video.layout="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" +AdvSceneSwitcher.condition.video.layout.modelPath="Dados do modelo (classificador em cascata haar):{{modelDataPath}}" +AdvSceneSwitcher.condition.video.layout.minNeighbor="Vizinhos mínimos:{{minNeighbors}}" +AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduzir carga de CPU realizando a verificação apenas a cada {{throttleCount}} milissegundos" +AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Realizar verificação apenas na área" +AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" +AdvSceneSwitcher.condition.video.layout.ocrColorPick="Verificar cor do texto:{{textColor}}{{selectColor}}" +AdvSceneSwitcher.condition.video.layout.ocrTextType="Verificar tipo de texto:{{textType}}" +AdvSceneSwitcher.condition.video.layout.ocrLanguage="Verificar idioma:{{languageCode}}" +AdvSceneSwitcher.condition.video.layout.color="Verificar cor:{{color}}{{selectColor}}" AdvSceneSwitcher.condition.video.minSize="Tamanho mínimo:" AdvSceneSwitcher.condition.video.maxSize="Tamanho máximo:" AdvSceneSwitcher.condition.video.selectArea="Selecionar área" diff --git a/data/locale/tr-TR.ini b/data/locale/tr-TR.ini index a3fae935..05012271 100644 --- a/data/locale/tr-TR.ini +++ b/data/locale/tr-TR.ini @@ -139,10 +139,10 @@ AdvSceneSwitcher.condition.video.screenshotFail="Kaynağın ekran görüntüsü AdvSceneSwitcher.condition.video.patternMatchFail="Desen bulunamadı!" AdvSceneSwitcher.condition.video.objectMatchFail="Nesne bulunamadı!" AdvSceneSwitcher.condition.video.modelLoadFail="Model verileri yüklenemedi!" -AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" -AdvSceneSwitcher.condition.video.entry.modelPath="Model verileri (haar kademeli sınıflandırıcı):{{modelDataPath}}" -AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum komşular: {{minNeighbors}}" -AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}} Yalnızca her seferinde kontrol gerçekleştirerek CPU yükünü azaltın {{throttleCount}} millisaniyeler" +AdvSceneSwitcher.condition.video.layout="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" +AdvSceneSwitcher.condition.video.layout.modelPath="Model verileri (haar kademeli sınıflandırıcı):{{modelDataPath}}" +AdvSceneSwitcher.condition.video.layout.minNeighbor="Minimum komşular: {{minNeighbors}}" +AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}} Yalnızca her seferinde kontrol gerçekleştirerek CPU yükünü azaltın {{throttleCount}} millisaniyeler" AdvSceneSwitcher.condition.stream="Yayınlama" AdvSceneSwitcher.condition.stream.state.start="Yayın çalışıyor" AdvSceneSwitcher.condition.stream.state.stop="Yayın durdu" diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini index 50bc7340..25e2042c 100644 --- a/data/locale/zh-CN.ini +++ b/data/locale/zh-CN.ini @@ -395,16 +395,16 @@ AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription="颜色与 AdvSceneSwitcher.condition.video.type.main="OBS的主输出" AdvSceneSwitcher.condition.video.type.source="源" AdvSceneSwitcher.condition.video.type.scene="场景" -AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" -AdvSceneSwitcher.condition.video.entry.modelPath="模型数据 (haar级联分类器): {{modelDataPath}}" -AdvSceneSwitcher.condition.video.entry.minNeighbor="最小区域: {{minNeighbors}}" -AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}每隔{{throttleCount}} 毫秒,才执行一次检查,从而减少 CPU 负载." -AdvSceneSwitcher.condition.video.entry.checkAreaEnable="仅在区域内执行检查" -AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" -AdvSceneSwitcher.condition.video.entry.orcColorPick="文本检查颜色:{{textColor}}{{selectColor}}" -AdvSceneSwitcher.condition.video.entry.orcTextType="文本检查类型:{{textType}}" -AdvSceneSwitcher.condition.video.entry.orcLanguage="语言检查:{{languageCode}}" -AdvSceneSwitcher.condition.video.entry.color="颜色检查:{{color}}{{selectColor}}" +AdvSceneSwitcher.condition.video.layout="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" +AdvSceneSwitcher.condition.video.layout.modelPath="模型数据 (haar级联分类器): {{modelDataPath}}" +AdvSceneSwitcher.condition.video.layout.minNeighbor="最小区域: {{minNeighbors}}" +AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}每隔{{throttleCount}} 毫秒,才执行一次检查,从而减少 CPU 负载." +AdvSceneSwitcher.condition.video.layout.checkAreaEnable="仅在区域内执行检查" +AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" +AdvSceneSwitcher.condition.video.layout.ocrColorPick="文本检查颜色:{{textColor}}{{selectColor}}" +AdvSceneSwitcher.condition.video.layout.ocrTextType="文本检查类型:{{textType}}" +AdvSceneSwitcher.condition.video.layout.ocrLanguage="语言检查:{{languageCode}}" +AdvSceneSwitcher.condition.video.layout.color="颜色检查:{{color}}{{selectColor}}" AdvSceneSwitcher.condition.video.minSize="最小尺寸:" AdvSceneSwitcher.condition.video.maxSize="最大尺寸:" AdvSceneSwitcher.condition.video.selectArea="选择区域" diff --git a/plugins/video/macro-condition-video.cpp b/plugins/video/macro-condition-video.cpp index 9ad84de9..26695493 100644 --- a/plugins/video/macro-condition-video.cpp +++ b/plugins/video/macro-condition-video.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -370,12 +371,16 @@ bool MacroConditionVideo::CheckOCR() auto text = RunOCR(_ocrParameters.GetOCR(), _screenshotData.GetImage(), _ocrParameters.color, _ocrParameters.colorThreshold); - SetVariableValue(text); - SetTempVarValue("text", text); + 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); + return _ocrParameters.regex.Matches(*text, _ocrParameters.text); } bool MacroConditionVideo::CheckColor() @@ -520,7 +525,7 @@ BrightnessEdit::BrightnessEdit(QWidget *parent, obs_module_text( "AdvSceneSwitcher.condition.video.brightnessThresholdDescription"))), _current(new QLabel), - _data(data) + _entryData(data) { auto layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); @@ -538,7 +543,7 @@ BrightnessEdit::BrightnessEdit(QWidget *parent, &BrightnessEdit::UpdateCurrentBrightness); _timer.start(1000); - _threshold->SetDoubleValue(_data->_brightnessThreshold); + _threshold->SetDoubleValue(_entryData->_brightnessThreshold); _loading = false; } @@ -546,17 +551,34 @@ void BrightnessEdit::UpdateCurrentBrightness() { QString text = obs_module_text( "AdvSceneSwitcher.condition.video.currentBrightness"); - _current->setText(text.arg(_data->GetCurrentBrightness())); + _current->setText(text.arg(_entryData->GetCurrentBrightness())); } void BrightnessEdit::BrightnessThresholdChanged(const DoubleVariable &value) { - if (_loading || !_data) { - return; + 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(); } - auto lock = LockContext(); - _data->_brightnessThreshold = value; + QUrl fileUrl = QUrl::fromLocalFile(path); + if (!QDesktopServices::openUrl(fileUrl)) { + DisplayMessage(obs_module_text( + "AdvSceneSwitcher.condition.video.ocrOpenConfig.openFailed")); + } } OCREdit::OCREdit(QWidget *parent, PreviewDialog *previewDialog, @@ -577,11 +599,25 @@ OCREdit::OCREdit(QWidget *parent, PreviewDialog *previewDialog, _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), - _data(data) + _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( @@ -601,6 +637,29 @@ OCREdit::OCREdit(QWidget *parent, PreviewDialog *previewDialog, 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) { + _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 widgetPlaceholders = { {"{{textColor}}", _textColor}, @@ -608,6 +667,10 @@ OCREdit::OCREdit(QWidget *parent, PreviewDialog *previewDialog, {"{{textType}}", _pageSegMode}, {"{{tesseractBaseDir}}", _tesseractBaseDir}, {"{{languageCode}}", _languageCode}, + {"{{configFile}}", _configFile}, + {"{{openConfigFile}}", _openConfigFile}, + {"{{reloadConfig}}", _reloadConfig}, + {"{{configFileHint}}", configFileHint}, }; auto layout = new QVBoxLayout(); @@ -620,39 +683,51 @@ OCREdit::OCREdit(QWidget *parent, PreviewDialog *previewDialog, auto pageModeSegLayout = new QHBoxLayout(); PlaceWidgets( obs_module_text( - "AdvSceneSwitcher.condition.video.entry.orcTextType"), + "AdvSceneSwitcher.condition.video.layout.ocrTextType"), pageModeSegLayout, widgetPlaceholders); layout->addLayout(pageModeSegLayout); auto baseDirLayout = new QHBoxLayout(); PlaceWidgets( obs_module_text( - "AdvSceneSwitcher.condition.video.entry.orcBaseDir"), + "AdvSceneSwitcher.condition.video.layout.ocrBaseDir"), baseDirLayout, widgetPlaceholders, false); layout->addLayout(baseDirLayout); auto languageLayout = new QHBoxLayout(); PlaceWidgets( obs_module_text( - "AdvSceneSwitcher.condition.video.entry.orcLanguage"), + "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.entry.orcColorPick"), + "AdvSceneSwitcher.condition.video.layout.ocrColorPick"), 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); + _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(_data->_ocrParameters.GetPageMode()))); + static_cast(_entryData->_ocrParameters.GetPageMode()))); _tesseractBaseDir->SetPath( - _data->_ocrParameters.GetTesseractBasePath()); - _languageCode->setText(_data->_ocrParameters.GetLanguageCode()); + _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; } @@ -665,12 +740,12 @@ void OCREdit::SetupColorLabel(const QColor &color) void OCREdit::SelectColorClicked() { - if (_loading || !_data) { + if (_loading || !_entryData) { return; } const QColor color = QColorDialog::getColor( - _data->_ocrParameters.color, this, + _entryData->_ocrParameters.color, this, obs_module_text("AdvSceneSwitcher.condition.video.selectColor"), QColorDialog::ColorDialogOption()); @@ -680,73 +755,54 @@ void OCREdit::SelectColorClicked() SetupColorLabel(color); auto lock = LockContext(); - _data->_ocrParameters.color = color; + _entryData->_ocrParameters.color = color; - _previewDialog->OCRParametersChanged(_data->_ocrParameters); + _previewDialog->OCRParametersChanged(_entryData->_ocrParameters); } void OCREdit::ColorThresholdChanged(const DoubleVariable &value) { - if (_loading || !_data) { - return; - } + GUARD_LOADING_AND_LOCK(); + _entryData->_ocrParameters.colorThreshold = value; - auto lock = LockContext(); - _data->_ocrParameters.colorThreshold = value; - - _previewDialog->OCRParametersChanged(_data->_ocrParameters); + _previewDialog->OCRParametersChanged(_entryData->_ocrParameters); } void OCREdit::MatchTextChanged() { - if (_loading || !_data) { - return; - } - - auto lock = LockContext(); - _data->_ocrParameters.text = + GUARD_LOADING_AND_LOCK(); + _entryData->_ocrParameters.text = _matchText->toPlainText().toUtf8().constData(); adjustSize(); updateGeometry(); - _previewDialog->OCRParametersChanged(_data->_ocrParameters); + _previewDialog->OCRParametersChanged(_entryData->_ocrParameters); } void OCREdit::RegexChanged(const RegexConfig &conf) { - if (_loading || !_data) { - return; - } - - auto lock = LockContext(); - _data->_ocrParameters.regex = conf; + GUARD_LOADING_AND_LOCK(); + _entryData->_ocrParameters.regex = conf; adjustSize(); updateGeometry(); - _previewDialog->OCRParametersChanged(_data->_ocrParameters); + _previewDialog->OCRParametersChanged(_entryData->_ocrParameters); } void OCREdit::PageSegModeChanged(int idx) { - if (_loading || !_data) { - return; - } - - auto lock = LockContext(); - _data->SetPageSegMode(static_cast( + GUARD_LOADING_AND_LOCK(); + _entryData->SetPageSegMode(static_cast( _pageSegMode->itemData(idx).toInt())); - _previewDialog->OCRParametersChanged(_data->_ocrParameters); + _previewDialog->OCRParametersChanged(_entryData->_ocrParameters); } -void OCREdit::TesseractBaseDirChanged(const QString &path){ - if (_loading || !_data) { - return; - } - - auto lock = LockContext(); - if (!_data->SetTesseractBaseDir(path.toStdString())) { +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); @@ -755,34 +811,48 @@ void OCREdit::TesseractBaseDirChanged(const QString &path){ // Reset to previous value const QSignalBlocker b(this); - _tesseractBaseDir->SetPath(_data->_ocrParameters.GetTesseractBasePath()); + _tesseractBaseDir->SetPath( + _entryData->_ocrParameters.GetTesseractBasePath()); return; } - _previewDialog->OCRParametersChanged(_data->_ocrParameters); + _previewDialog->OCRParametersChanged(_entryData->_ocrParameters); } - void OCREdit::LanguageChanged() { - if (_loading || !_data) { - return; - } - - auto lock = LockContext(); - if (!_data->SetLanguageCode(_languageCode->text().toStdString())) { + 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( - _data->_ocrParameters.GetTesseractBasePath())); + _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(_data->_ocrParameters.GetLanguageCode()); + _languageCode->setText( + _entryData->_ocrParameters.GetLanguageCode()); return; } - _previewDialog->OCRParametersChanged(_data->_ocrParameters); + _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( @@ -802,7 +872,7 @@ ObjectDetectEdit::ObjectDetectEdit( _minSize(new SizeSelection(0, 1024)), _maxSize(new SizeSelection(0, 4096)), _previewDialog(previewDialog), - _data(data) + _entryData(data) { _minNeighbors->setMinimum(minMinNeighbors); _minNeighbors->setMaximum(maxMinNeighbors); @@ -833,14 +903,14 @@ ObjectDetectEdit::ObjectDetectEdit( pathLayout->setContentsMargins(0, 0, 0, 0); PlaceWidgets( obs_module_text( - "AdvSceneSwitcher.condition.video.entry.modelPath"), + "AdvSceneSwitcher.condition.video.layout.modelPath"), pathLayout, widgetPlaceholders); auto neighborsLayout = new QHBoxLayout; neighborsLayout->setContentsMargins(0, 0, 0, 0); PlaceWidgets( obs_module_text( - "AdvSceneSwitcher.condition.video.entry.minNeighbor"), + "AdvSceneSwitcher.condition.video.layout.minNeighbor"), neighborsLayout, widgetPlaceholders); auto sizeGrid = new QGridLayout; @@ -866,62 +936,50 @@ ObjectDetectEdit::ObjectDetectEdit( layout->addLayout(sizeLayout); setLayout(layout); - _modelDataPath->SetPath(_data->GetModelDataPath()); + _modelDataPath->SetPath(_entryData->GetModelDataPath()); _objectScaleThreshold->SetDoubleValue( - _data->_objMatchParameters.scaleFactor); - _minNeighbors->setValue(_data->_objMatchParameters.minNeighbors); - _minSize->SetSize(_data->_objMatchParameters.minSize); - _maxSize->SetSize(_data->_objMatchParameters.maxSize); + _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) { - if (_loading || !_data) { - return; - } - - auto lock = LockContext(); - _data->_objMatchParameters.scaleFactor = value; - _previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters); + GUARD_LOADING_AND_LOCK(); + _entryData->_objMatchParameters.scaleFactor = value; + _previewDialog->ObjDetectParametersChanged( + _entryData->_objMatchParameters); } void ObjectDetectEdit::MinNeighborsChanged(int value) { - if (_loading || !_data) { - return; - } - - auto lock = LockContext(); - _data->_objMatchParameters.minNeighbors = value; - _previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters); + GUARD_LOADING_AND_LOCK(); + _entryData->_objMatchParameters.minNeighbors = value; + _previewDialog->ObjDetectParametersChanged( + _entryData->_objMatchParameters); } void ObjectDetectEdit::MinSizeChanged(advss::Size value) { - if (_loading || !_data) { - return; - } - - auto lock = LockContext(); - _data->_objMatchParameters.minSize = value; - _previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters); + GUARD_LOADING_AND_LOCK(); + _entryData->_objMatchParameters.minSize = value; + _previewDialog->ObjDetectParametersChanged( + _entryData->_objMatchParameters); } void ObjectDetectEdit::MaxSizeChanged(advss::Size value) { - if (_loading || !_data) { - return; - } - - auto lock = LockContext(); - _data->_objMatchParameters.maxSize = value; - _previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters); + GUARD_LOADING_AND_LOCK(); + _entryData->_objMatchParameters.maxSize = value; + _previewDialog->ObjDetectParametersChanged( + _entryData->_objMatchParameters); } void ObjectDetectEdit::ModelPathChanged(const QString &text) { - if (_loading || !_data) { + if (_loading || !_entryData) { return; } @@ -929,13 +987,14 @@ void ObjectDetectEdit::ModelPathChanged(const QString &text) { auto lock = LockContext(); std::string path = text.toStdString(); - dataLoaded = _data->LoadModelData(path); + dataLoaded = _entryData->LoadModelData(path); } if (!dataLoaded) { DisplayMessage(obs_module_text( "AdvSceneSwitcher.condition.video.modelLoadFail")); } - _previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters); + _previewDialog->ObjDetectParametersChanged( + _entryData->_objMatchParameters); } ColorEdit::ColorEdit(QWidget *parent, @@ -958,7 +1017,7 @@ ColorEdit::ColorEdit(QWidget *parent, _color(new QLabel), _selectColor(new QPushButton(obs_module_text( "AdvSceneSwitcher.condition.video.selectColor"))), - _data(data) + _entryData(data) { QWidget::connect(_selectColor, SIGNAL(clicked()), this, SLOT(SelectColorClicked())); @@ -979,9 +1038,9 @@ ColorEdit::ColorEdit(QWidget *parent, }; auto colorLayout = new QHBoxLayout; - PlaceWidgets( - obs_module_text("AdvSceneSwitcher.condition.video.entry.color"), - colorLayout, widgetPlaceholders); + PlaceWidgets(obs_module_text( + "AdvSceneSwitcher.condition.video.layout.color"), + colorLayout, widgetPlaceholders); auto layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); @@ -990,9 +1049,11 @@ ColorEdit::ColorEdit(QWidget *parent, layout->addWidget(_matchThreshold); setLayout(layout); - _matchThreshold->SetDoubleValue(_data->_colorParameters.matchThreshold); - _colorThreshold->SetDoubleValue(_data->_colorParameters.colorThreshold); - SetupColorLabel(_data->_colorParameters.color); + _matchThreshold->SetDoubleValue( + _entryData->_colorParameters.matchThreshold); + _colorThreshold->SetDoubleValue( + _entryData->_colorParameters.colorThreshold); + SetupColorLabel(_entryData->_colorParameters.color); _loading = false; } @@ -1005,32 +1066,24 @@ void ColorEdit::SetupColorLabel(const QColor &color) void ColorEdit::MatchThresholdChanged(const DoubleVariable &value) { - if (_loading || !_data) { - return; - } - - auto lock = LockContext(); - _data->_colorParameters.matchThreshold = value; + GUARD_LOADING_AND_LOCK(); + _entryData->_colorParameters.matchThreshold = value; } void ColorEdit::ColorThresholdChanged(const DoubleVariable &value) { - if (_loading || !_data) { - return; - } - - auto lock = LockContext(); - _data->_colorParameters.colorThreshold = value; + GUARD_LOADING_AND_LOCK(); + _entryData->_colorParameters.colorThreshold = value; } void ColorEdit::SelectColorClicked() { - if (_loading || !_data) { + if (_loading || !_entryData) { return; } const QColor color = QColorDialog::getColor( - _data->_colorParameters.color, this, + _entryData->_colorParameters.color, this, obs_module_text("AdvSceneSwitcher.condition.video.selectColor"), QColorDialog::ColorDialogOption()); @@ -1040,19 +1093,19 @@ void ColorEdit::SelectColorClicked() SetupColorLabel(color); auto lock = LockContext(); - _data->_colorParameters.color = color; + _entryData->_colorParameters.color = color; } AreaEdit::AreaEdit(QWidget *parent, PreviewDialog *previewDialog, const std::shared_ptr &data) : QWidget(parent), _checkAreaEnable(new QCheckBox(obs_module_text( - "AdvSceneSwitcher.condition.video.entry.checkAreaEnable"))), + "AdvSceneSwitcher.condition.video.layout.checkAreaEnable"))), _checkArea(new AreaSelection(0, 99999)), _selectArea(new QPushButton(obs_module_text( "AdvSceneSwitcher.condition.video.selectArea"))), _previewDialog(previewDialog), - _data(data) + _entryData(data) { QWidget::connect(_checkAreaEnable, SIGNAL(stateChanged(int)), this, SLOT(CheckAreaEnableChanged(int))); @@ -1073,20 +1126,20 @@ AreaEdit::AreaEdit(QWidget *parent, PreviewDialog *previewDialog, layout->setContentsMargins(0, 0, 0, 0); PlaceWidgets( obs_module_text( - "AdvSceneSwitcher.condition.video.entry.checkArea"), + "AdvSceneSwitcher.condition.video.layout.checkArea"), layout, widgetPlaceholders); setLayout(layout); - _checkAreaEnable->setChecked(_data->_areaParameters.enable); - _checkArea->SetArea(_data->_areaParameters.area); + _checkAreaEnable->setChecked(_entryData->_areaParameters.enable); + _checkArea->SetArea(_entryData->_areaParameters.area); SetWidgetVisibility(); _loading = false; } void AreaEdit::SetWidgetVisibility() { - _checkArea->setVisible(_data->_areaParameters.enable); - _selectArea->setVisible(_data->_areaParameters.enable); + _checkArea->setVisible(_entryData->_areaParameters.enable); + _selectArea->setVisible(_entryData->_areaParameters.enable); adjustSize(); updateGeometry(); } @@ -1101,26 +1154,18 @@ void AreaEdit::SelectAreaClicked() void AreaEdit::CheckAreaEnableChanged(int value) { - if (_loading || !_data) { - return; - } - - auto lock = LockContext(); - _data->_areaParameters.enable = value; + GUARD_LOADING_AND_LOCK(); + _entryData->_areaParameters.enable = value; SetWidgetVisibility(); - _previewDialog->AreaParametersChanged(_data->_areaParameters); + _previewDialog->AreaParametersChanged(_entryData->_areaParameters); emit Resized(); } void AreaEdit::CheckAreaChanged(Area value) { - if (_loading || !_data) { - return; - } - - auto lock = LockContext(); - _data->_areaParameters.area = value; - _previewDialog->AreaParametersChanged(_data->_areaParameters); + GUARD_LOADING_AND_LOCK(); + _entryData->_areaParameters.area = value; + _previewDialog->AreaParametersChanged(_entryData->_areaParameters); } void AreaEdit::CheckAreaChanged(QRect rect) @@ -1261,15 +1306,16 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( {"{{throttleCount}}", _throttleCount}, {"{{patternMatchingModes}}", _patternMatchMode}, }; - PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.video.entry"), + 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.entry.throttle"), - _throttleControlLayout, widgetPlaceholders); + PlaceWidgets( + obs_module_text( + "AdvSceneSwitcher.condition.video.layout.throttle"), + _throttleControlLayout, widgetPlaceholders); QHBoxLayout *showMatchLayout = new QHBoxLayout; showMatchLayout->addWidget(_showMatch); @@ -1323,33 +1369,21 @@ void MacroConditionVideoEdit::UpdatePreviewTooltip() void MacroConditionVideoEdit::SourceChanged(const SourceSelection &source) { - if (_loading || !_entryData) { - return; - } - - auto lock = LockContext(); + GUARD_LOADING_AND_LOCK(); _entryData->_video.source = source; HandleVideoInputUpdate(); } void MacroConditionVideoEdit::SceneChanged(const SceneSelection &scene) { - if (_loading || !_entryData) { - return; - } - - auto lock = LockContext(); + GUARD_LOADING_AND_LOCK(); _entryData->_video.scene = scene; HandleVideoInputUpdate(); } void MacroConditionVideoEdit::VideoInputTypeChanged(int type) { - if (_loading || !_entryData) { - return; - } - - auto lock = LockContext(); + GUARD_LOADING_AND_LOCK(); _entryData->_video.type = static_cast(type); HandleVideoInputUpdate(); SetWidgetVisibility(); @@ -1365,11 +1399,7 @@ void MacroConditionVideoEdit::HandleVideoInputUpdate() void MacroConditionVideoEdit::ConditionChanged(int idx) { - if (_loading || !_entryData) { - return; - } - - auto lock = LockContext(); + GUARD_LOADING_AND_LOCK(); _entryData->SetCondition( static_cast(_condition->itemData(idx).toInt())); _entryData->ResetLastMatch(); @@ -1397,11 +1427,7 @@ void MacroConditionVideoEdit::ConditionChanged(int idx) void MacroConditionVideoEdit::ImagePathChanged(const QString &text) { - if (_loading || !_entryData) { - return; - } - - auto lock = LockContext(); + GUARD_LOADING_AND_LOCK(); _entryData->_file = text.toUtf8().constData(); _entryData->ResetLastMatch(); if (_entryData->LoadImageFromFile()) { diff --git a/plugins/video/macro-condition-video.hpp b/plugins/video/macro-condition-video.hpp index c6533495..f5b50637 100644 --- a/plugins/video/macro-condition-video.hpp +++ b/plugins/video/macro-condition-video.hpp @@ -128,7 +128,7 @@ private: QLabel *_current; QTimer _timer; - std::shared_ptr _data; + std::shared_ptr _entryData; bool _loading = true; }; @@ -147,6 +147,8 @@ private slots: void PageSegModeChanged(int); void TesseractBaseDirChanged(const QString &); void LanguageChanged(); + void UseConfigChanged(int); + void ConfigFileChanged(const QString &); private: void SetupColorLabel(const QColor &); @@ -159,10 +161,15 @@ private: QComboBox *_pageSegMode; FileSelection *_tesseractBaseDir; VariableLineEdit *_languageCode; + QCheckBox *_useConfig; + FileSelection *_configFile; + QPushButton *_openConfigFile; + QPushButton *_reloadConfig; + QHBoxLayout *_configLayout; PreviewDialog *_previewDialog; - std::shared_ptr _data; + std::shared_ptr _entryData; bool _loading = true; }; @@ -190,7 +197,7 @@ private: PreviewDialog *_previewDialog; - std::shared_ptr _data; + std::shared_ptr _entryData; bool _loading = true; }; @@ -214,7 +221,7 @@ private: QLabel *_color; QPushButton *_selectColor; - std::shared_ptr _data; + std::shared_ptr _entryData; bool _loading = true; }; @@ -243,7 +250,7 @@ private: PreviewDialog *_previewDialog; - std::shared_ptr _data; + std::shared_ptr _entryData; bool _loading = true; }; diff --git a/plugins/video/opencv-helpers.cpp b/plugins/video/opencv-helpers.cpp index c8a23e6a..ffacc256 100644 --- a/plugins/video/opencv-helpers.cpp +++ b/plugins/video/opencv-helpers.cpp @@ -202,14 +202,15 @@ cv::Mat PreprocessForOCR(const QImage &image, const QColor &textColor, return result; } -std::string RunOCR(tesseract::TessBaseAPI *ocr, const QImage &image, - const QColor &color, double colorDiff) +std::optional RunOCR(tesseract::TessBaseAPI *ocr, + const QImage &image, const QColor &color, + double colorDiff) { (void)ocr; (void)color; (void)colorDiff; if (image.isNull()) { - return ""; + return {}; } #ifdef OCR_SUPPORT @@ -221,12 +222,12 @@ std::string RunOCR(tesseract::TessBaseAPI *ocr, const QImage &image, std::unique_ptr detectedText(ocr->GetUTF8Text()); if (!detectedText) { - return ""; + return {}; } return detectedText.get(); #else - return ""; + return {}; #endif } diff --git a/plugins/video/opencv-helpers.hpp b/plugins/video/opencv-helpers.hpp index a5082533..5293c23e 100644 --- a/plugins/video/opencv-helpers.hpp +++ b/plugins/video/opencv-helpers.hpp @@ -27,10 +27,23 @@ enum PageSegMode { PSM_COUNT }; +enum OcrEngineMode { + OEM_TESSERACT_ONLY, + OEM_LSTM_ONLY, + OEM_TESSERACT_LSTM_COMBINED, + OEM_DEFAULT, + OEM_COUNT // Number of OEMs +}; + class TessBaseAPI { public: void SetPageSegMode(PageSegMode) {} - int Init(const char *, const char *) { return 0; } + int Init(const char *, const char *, OcrEngineMode, char **, int, + const std::vector *, + const std::vector *, bool) + { + return 0; + } void End() {} }; } // namespace tesseract @@ -62,8 +75,8 @@ std::vector MatchObject(QImage &img, cv::CascadeClassifier &cascade, uchar GetAvgBrightness(QImage &img); cv::Mat PreprocessForOCR(const QImage &image, const QColor &color, double colorDiff); -std::string RunOCR(tesseract::TessBaseAPI *, const QImage &, const QColor &, - double colorDiff); +std::optional RunOCR(tesseract::TessBaseAPI *, const QImage &, + const QColor &, double colorDiff); bool ContainsPixelsInColorRange(const QImage &image, const QColor &color, double colorDeviationThreshold, double totalPixelMatchThreshold); diff --git a/plugins/video/parameter-wrappers.cpp b/plugins/video/parameter-wrappers.cpp index 8a7c3ec2..c3784e66 100644 --- a/plugins/video/parameter-wrappers.cpp +++ b/plugins/video/parameter-wrappers.cpp @@ -1,4 +1,5 @@ #include "parameter-wrappers.hpp" +#include "log-helper.hpp" #include #include @@ -253,14 +254,13 @@ OCRParameters::OCRParameters(const OCRParameters &other) colorThreshold(other.colorThreshold), pageSegMode(other.pageSegMode), tesseractBasePath(other.tesseractBasePath), - languageCode(other.languageCode) + languageCode(other.languageCode), + useConfig(other.useConfig), + configFile(other.configFile) { if (!initDone) { Setup(); } - if (initDone) { - ocr->SetPageSegMode(pageSegMode); - } } OCRParameters &OCRParameters::operator=(const OCRParameters &other) @@ -272,12 +272,11 @@ OCRParameters &OCRParameters::operator=(const OCRParameters &other) pageSegMode = other.pageSegMode; tesseractBasePath = other.tesseractBasePath; languageCode = other.languageCode; + useConfig = other.useConfig; + configFile = other.configFile; if (!initDone) { Setup(); } - if (initDone) { - ocr->SetPageSegMode(pageSegMode); - } return *this; } @@ -288,6 +287,8 @@ bool OCRParameters::Save(obs_data_t *obj) const regex.Save(data); tesseractBasePath.Save(data, "tesseractBasePath"); languageCode.Save(data, "language"); + obs_data_set_bool(data, "useConfig", useConfig); + obs_data_set_string(data, "configFile", configFile.c_str()); SaveColor(data, "textColor", color); colorThreshold.Save(data, "colorThreshold"); obs_data_set_int(data, "pageSegMode", static_cast(pageSegMode)); @@ -311,6 +312,8 @@ bool OCRParameters::Load(obs_data_t *obj) obs_current_module())) + "/res/ocr/"; } + useConfig = obs_data_get_bool(data, "useConfig"); + configFile = obs_data_get_string(data, "configFile"); color = LoadColor(data, "textColor"); if (obs_data_has_user_value(data, "version")) { @@ -320,9 +323,6 @@ bool OCRParameters::Load(obs_data_t *obj) obs_data_get_int(data, "pageSegMode")); obs_data_release(data); - if (initDone) { - ocr->SetPageSegMode(pageSegMode); - } Setup(); return true; } @@ -330,7 +330,9 @@ bool OCRParameters::Load(obs_data_t *obj) void OCRParameters::SetPageMode(tesseract::PageSegMode mode) { pageSegMode = mode; - ocr->SetPageSegMode(mode); + if (ocr) { + ocr->SetPageSegMode(mode); + } } bool OCRParameters::SetLanguageCode(const std::string &value) @@ -343,7 +345,6 @@ bool OCRParameters::SetLanguageCode(const std::string &value) } languageCode = value; Setup(); - ocr->SetPageSegMode(pageSegMode); return true; } @@ -363,7 +364,6 @@ bool OCRParameters::SetTesseractBasePath(const std::string &value) } tesseractBasePath = value; Setup(); - ocr->SetPageSegMode(pageSegMode); return true; } @@ -372,6 +372,18 @@ std::string OCRParameters::GetTesseractBasePath() const return tesseractBasePath; } +void OCRParameters::EnableCustomConfig(bool enable) +{ + useConfig = enable; + Setup(); +} + +void OCRParameters::SetCustomConfigFile(const std::string &filename) +{ + configFile = filename; + Setup(); +} + void OCRParameters::Setup() { ocr = std::make_unique(); @@ -383,18 +395,41 @@ void OCRParameters::Setup() const std::string dataPath = std::string(tesseractBasePath) + "/"; const std::string modelFile = std::string(languageCode) + ".traineddata"; - const auto fullPath = QString::fromStdString(dataPath) + - QString::fromStdString(modelFile); - QFileInfo fileInfo(fullPath); - if (!fileInfo.exists(fullPath)) { + const auto modelFullPath = QString::fromStdString(dataPath) + + QString::fromStdString(modelFile); + QFileInfo modelFileInfo(modelFullPath); + if (!modelFileInfo.exists(modelFullPath)) { + initDone = false; + blog(LOG_WARNING, + "cannot init tesseract! Model path does not exists: %s", + modelFileInfo.absoluteFilePath().toStdString().c_str()); + return; + } + + auto configPath = QString::fromStdString(configFile); + QFileInfo configFileInfo(configPath); + bool configFileExists = configFileInfo.exists(configPath); + + bool setupWithConfig = useConfig; + if (useConfig && !configFileExists) { + blog(LOG_WARNING, + "tesseract config file will be ignored! File does not exists: %s", + configFileInfo.absoluteFilePath().toStdString().c_str()); + setupWithConfig = false; + } + + char *configs[] = {configFile.data()}; + if (ocr->Init(dataPath.c_str(), languageCode.c_str(), + tesseract::OEM_DEFAULT, + setupWithConfig ? configs : nullptr, + setupWithConfig ? 1 : 0, nullptr, nullptr, false) != 0) { + blog(LOG_WARNING, "tesseract init failed!"); initDone = false; return; } - if (ocr->Init(dataPath.c_str(), languageCode.c_str()) != 0) { - initDone = false; - return; - } + ocr->SetPageSegMode(pageSegMode); + initDone = true; } diff --git a/plugins/video/parameter-wrappers.hpp b/plugins/video/parameter-wrappers.hpp index f905e6e9..4d758c35 100644 --- a/plugins/video/parameter-wrappers.hpp +++ b/plugins/video/parameter-wrappers.hpp @@ -95,6 +95,10 @@ public: std::string GetLanguageCode() const; bool SetTesseractBasePath(const std::string &); std::string GetTesseractBasePath() const; + void EnableCustomConfig(bool enable); + bool CustomConfigIsEnabled() const { return useConfig; } + void SetCustomConfigFile(const std::string &); + std::string GetCustomConfigFile() const { return configFile; } tesseract::PageSegMode GetPageMode() const { return pageSegMode; } tesseract::TessBaseAPI *GetOCR() const { return ocr.get(); } @@ -111,6 +115,8 @@ private: obs_get_module_data_path(obs_current_module()) + std::string("/res/ocr"); StringVariable languageCode = "eng"; + bool useConfig = false; + std::string configFile = "config.txt"; std::unique_ptr ocr; bool initDone = false; }; diff --git a/plugins/video/preview-dialog.cpp b/plugins/video/preview-dialog.cpp index 260f644b..c48b47ff 100644 --- a/plugins/video/preview-dialog.cpp +++ b/plugins/video/preview-dialog.cpp @@ -353,9 +353,15 @@ void PreviewImage::MarkMatch(QImage &screenshot, } else if (condition == VideoCondition::OCR) { auto text = RunOCR(ocrParams.GetOCR(), screenshot, ocrParams.color, ocrParams.colorThreshold); + + if (!text) { + emit StatusUpdate(obs_module_text( + "AdvSceneSwitcher.condition.video.ocrMatchFail")); + } + QString status(obs_module_text( "AdvSceneSwitcher.condition.video.ocrMatchSuccess")); - emit StatusUpdate(status.arg(QString::fromStdString(text))); + emit StatusUpdate(status.arg(QString::fromStdString(*text))); } }