Utility-Skripte¶
Automatisch generierte Übersicht der Hilfsskripte.
Setup-Skripte¶
setup/SystemCheck.ps1¶
# SystemCheck.ps1
Write-Host "--- STARTE SYSTEM-DIAGNOSE ---" -ForegroundColor Cyan
# 1. PFAD DEFINITIONEN
# $tempDir = "$env:TEMP\VoskTTS_Check"
$tempDir = "C:\tmp\sl5_aura"
$testFile = "$tempDir\write_test.txt"
$pythonPath = ".\.venv\Scripts\python.exe"
# 2. SCHREIBTEST (Simuliert Vosk Service)
Write-Host "[1/3] Prüfe Schreibrechte für Python..." -NoNewline
if (-not (Test-Path $tempDir)) { New-Item -ItemType Directory -Path $tempDir -Force | Out-Null }
try {
# Versuch, eine Datei zu schreiben
"Test-Inhalt" | Out-File -FilePath $testFile -Encoding utf8
if (Test-Path $testFile) {
Write-Host " OK (Ordner und Datei erzeugbar)" -ForegroundColor Green
} else {
throw "Datei konnte nicht erstellt werden."
}
} catch {
Write-Host " FEHLER" -ForegroundColor Red
Write-Host " Ursache: Windows Defender oder fehlende Rechte blockieren das Schreiben in $tempDir" -ForegroundColor Yellow
Write-Host " Lösung: Fügen Sie den Ordner zu den Ausnahmen im Defender hinzu."
exit
}
# 3. AHK ADMIN CHECK
Write-Host "[2/3] Prüfe AHK Berechtigungen..."
# Wir prüfen, ob der aktuelle Prozess Admin-Rechte hat.
# AHK erbt diese Rechte, wenn es von hier gestartet wird.
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
if ($isAdmin) {
Write-Host " OK (Läuft als Admin)" -ForegroundColor Green
} else {
Write-Host " WARNUNG: Skript läuft NICHT als Admin." -ForegroundColor Yellow
Write-Host " Das bedeutet: AHK darf NICHT in Programme schreiben, die als Administrator gestartet wurden." -ForegroundColor Yellow
Write-Host " Lösung: Starten Sie ALLES (Vosk & AHK) als Administrator." -ForegroundColor Yellow
}
# 4. TYPING TEST (Visueller Test)
Write-Host "[3/3] Simuliere Tippen..."
Write-Host " Ein Notepad Fenster öffnet sich gleich. Wenn dort Text erscheint, funktioniert alles." -ForegroundColor Cyan
Start-Sleep -Seconds 2
# Notepad öffnen
$notepad = Start-Process notepad.exe -PassThru
Start-Sleep -Seconds 1
# SendKeys via PowerShell nutzen (ähnlich wie AHK, um zu testen ob Windows Input blockt)
try {
[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
[System.Windows.Forms.SendKeys]::SendWait("Test OK")
# Write-Host " Wurde Text in Notepad geschrieben? (Ja/Nein)"
} catch {
Write-Host " FEHLER beim Senden von Tastenanschlägen." -ForegroundColor Red
Pause
}
Write-Host "--- DIAGNOSE ENDE ---"
Start-Sleep -Seconds 1
# Fenster schließen mit Alt+F4
#[System.Windows.Forms.SendKeys]::SendWait("%{F4}")
# Kill all notepad without save !!
#Get-Process notepad -ErrorAction SilentlyContinue | Stop-Process -Force # thats also has killed the Terminals
#$procs = Get-Process -Name notepad -ErrorAction SilentlyContinue
#$procs | ForEach-Object { Stop-Process -Id $_.Id -Force }
Add-Type -AssemblyName System.Windows.Forms
# Alt+F4 (schließt Notepad) und dann 'n' für "Nicht speichern"
[System.Windows.Forms.SendKeys]::SendWait("%{F4}")
Start-Sleep -Milliseconds 120
[System.Windows.Forms.SendKeys]::SendWait("n")
#Pause
setup/UNINSTALL.txt¶
--------------------------------------------------------------------------------
ENGLISH
--------------------------------------------------------------------------------
How to uninstall SL5 Aura:
1. Stop the application:
Close the running Python window or terminate the process.
2. Remove the Main System:
Simply DELETE the entire folder containing this file.
SL5 Aura is portable and runs within its own virtual environment.
It does not leave registry entries or hidden system files for the core engine.
3. Optional - Remove Client Tools:
If you installed the optional helper tools (AutoHotkey & CopyQ) via our setup,
you can run the script located at:
setup/uninstall_ahk_copyq.bat
--------------------------------------------------------------------------------
DEUTSCH
--------------------------------------------------------------------------------
So deinstallieren Sie SL5 Aura:
1. Anwendung beenden:
Schließen Sie das laufende Python-Fenster oder beenden Sie den Prozess.
2. Hauptsystem entfernen:
LÖSCHEN Sie einfach den gesamten Ordner, in dem sich diese Datei befindet.
SL5 Aura ist "portable" und läuft in seiner eigenen virtuellen Umgebung.
Es hinterlässt keine Registry-Einträge oder versteckte Systemdateien für die Core-Engine.
3. Optional - Client-Tools entfernen:
Falls Sie die optionalen Hilfsprogramme (AutoHotkey & CopyQ) über unser Setup
installiert haben, können Sie dieses Skript ausführen:
setup/uninstall_ahk_copyq.bat
setup/fix_permissions.bat¶
@echo off
:: setup/fix_permissions.bat
pushd "%~dp0"
cd ..
set "TARGET_DIR=%CD%"
popd
echo Ziel: "%TARGET_DIR%"
:: Wir setzen die Rechte NUR auf den Hauptordner.
:: (OI)(CI) sorgt dafuer, dass alle Unterordner/Dateien das automatisch erben.
:: Das dauert nur 1 Sekunde.
:: Deutsch
icacls "%TARGET_DIR%" /grant:r "Benutzer":(OI)(CI)F /grant:r "Jeder":(OI)(CI)F
:: Englisch / International
icacls "%TARGET_DIR%" /grant:r "Users":(OI)(CI)F /grant:r "Everyone":(OI)(CI)F
set "TARGET_DIR=C:\tmp\sl5_aura"
:: 1. Verzeichnis erstellen (falls nicht vorhanden)
if not exist "%TARGET_DIR%" mkdir "%TARGET_DIR%"
:: 2. Rechte setzen
:: /grant = Rechte gewähren
:: *S-1-5-32-545 = SID für die Gruppe "Benutzer" (lokal) - sicherer als "Jeder"
:: (OI) = Object Inherit (Dateien erben Rechte)
:: (CI) = Container Inherit (Unterordner erben Rechte)
:: F = Full Control (Vollzugriff - Lesen, Schreiben, Löschen)
:: /t = Rekursiv auf bereits existierende Unterelemente anwenden
:: /grant *S-1-1-0:(OI)(CI)F = JEDER (Everyone) bekommt Vollzugriff auf Dateien und Unterordner
:: icacls "%TARGET_DIR%" /grant *S-1-1-0:(OI)(CI)F /t /c /q
icacls "%TARGET_DIR%" /grant *S-1-5-32-545:(OI)(CI)F /t
net session >nul 2>&1
if %errorLevel% neq 0 (
echo ACHTUNG: Keine Admin-Rechte!
echo Bitte prüfen ob das Script in C:\tmp\sl5_aura schreiben kann.
echo Eventuell noch mal starten so: Rechtsklick auf die Datei -> "Als Administrator ausfuehren"
timeout /t 4
)
echo Rechte für %TARGET_DIR% verarbeitet.
echo.
echo ========================================================
echo Fertig! Rechte wurden (hoffentlich) per Vererbung gesetzt.
echo ========================================================
setup/glogg_installer.bat¶
@echo off
cd /d "%~dp0"
set INSTALLER=glogg-latest-x86_64-setup.exe
set URL=http://glogg.bonnefon.org/files/%INSTALLER%
if exist "%ProgramFiles%\glogg\glogg.exe" (
echo glogg is already installed.
exit /b
)
echo Downloading %INSTALLER%...
powershell -Command "Invoke-WebRequest -Uri '%URL%' -OutFile '%INSTALLER%'"
echo Installing glogg...
:: /S = Silent
:: /D=Path = Optional output directory
"%INSTALLER%" /S
del "%INSTALLER%"
echo glogg installation complete.
reg add "HKCR\*\shell\Open with glogg" /ve /d "Open with glogg" /f
reg add "HKCR\*\shell\Open with glogg\command" /ve /d "\"C:\Program Files\glogg\glogg.exe\" \"%1\"" /f
setup/install_ahk_copyq.ps1¶
# install_ahk_copyq.ps1
# script_name: setup/install_ahk_copyq.ps1
param([string]$ProjectRootPath)
$downloadDir = "$env:USERPROFILE\Downloads"
# AHK specific variables
$ahkInstallerName = "ahk-install.exe"
$ahkLocalPath = Join-Path -Path $downloadDir -ChildPath $ahkInstallerName
$ahkInstalledPath = "$env:ProgramFiles\AutoHotkey\v2\AutoHotkey.exe"
$ProjectRootPath = Split-Path -Path $PSScriptRoot -Parent
function Register-AuraStartupTask {
param(
[string]$ProjectRootPath
)
$AhkExePath = Join-Path $env:ProgramFiles "AutoHotkey\v2\AutoHotkey64.exe"
if (-not (Test-Path $AhkExePath)) {
$AhkExePath = Join-Path $env:ProgramFiles "AutoHotkey\v2\AutoHotkey.exe"
}
$AhkScriptPath = Join-Path $ProjectRootPath "trigger-hotkeys.ahk"
if (-not (Test-Path $AhkExePath)) {
Write-Error "AutoHotkey executable not found at '$AhkExePath'. Cannot create scheduled task."
return
}
$TaskName = "AuraDictation_Hotkeys"
$TaskDescription = "Starts the AutoHotkey script for Aura Dictation with highest privileges on user logon."
$TaskAction = New-ScheduledTaskAction -Execute $AhkExePath -Argument "`"$AhkScriptPath`""
$TaskTrigger = New-ScheduledTaskTrigger -AtLogOn
$TaskPrincipal = New-ScheduledTaskPrincipal -UserId (Get-CimInstance Win32_ComputerSystem).Username -LogonType Interactive -RunLevel Highest
# Alte Aufgabe löschen, falls vorhanden, für eine saubere Neuinstallation
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue
# Neue Aufgabe registrieren
$TaskSettings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-DontStopOnIdleEnd `
-ExecutionTimeLimit 0
try {
Register-ScheduledTask -TaskName $TaskName `
-Action $TaskAction `
-Trigger $TaskTrigger `
-Principal $TaskPrincipal `
-Settings $TaskSettings `
-Description $TaskDescription
Write-Host "Successfully created scheduled task '$TaskName'."
}
catch {
Write-Error "Failed to create scheduled task. Error: $_"
}
}
Write-Host "Starting setup for client tools..." -ForegroundColor Cyan
# --- TASK 1: AutoHotkey V2 ---
Write-Host "`n[1/2] Checking AutoHotkey V2..." -ForegroundColor Yellow
if (Test-Path -Path $ahkInstalledPath) {
Write-Host "AutoHotkey V2 is already installed. Skipping." -ForegroundColor Green
} else {
# Not installed, check local download
if (Test-Path -Path $ahkLocalPath) {
Write-Host "Found installer in Downloads folder ($ahkInstallerName). Installing from local file..." -ForegroundColor Cyan
try {
# Start local installer silent
$process = Start-Process -FilePath $ahkLocalPath -ArgumentList "/silent" -Wait -PassThru
if ($process.ExitCode -eq 0) {
Write-Host "AutoHotkey installed from local file successfully." -ForegroundColor Green
} else {
Write-Warning "Local installer exited with code $($process.ExitCode). Trying Winget as fallback..."
winget install --id "AutoHotkey.AutoHotkey" -e --source winget --accept-package-agreements --accept-source-agreements
}
} catch {
Write-Error "Failed to run local installer. $_"
}
} else {
# Not installed, no local file -> Use Winget
Write-Host "No local installer found. Installing via Winget..." -ForegroundColor Cyan
winget install --id "AutoHotkey.AutoHotkey" -e --source winget --accept-package-agreements --accept-source-agreements
}
}
if ($ProjectRootPath) {
Register-AuraStartupTask -ProjectRootPath $ProjectRootPath
} else {
Write-Warning "ProjectRootPath path not provided. Cannot create scheduled task."
}
# --- TASK 2: CopyQ ---
Write-Host "`n[2/2] Checking CopyQ..." -ForegroundColor Yellow
# We rely on Winget's internal check here, as CopyQ installer filenames vary by version.
# Winget will detect if it's already installed and skip/update accordingly.
winget install --id "hluk.CopyQ" -e --source winget --accept-package-agreements --accept-source-agreements
if ($LASTEXITCODE -eq 0) {
Write-Host "CopyQ check/install completed successfully." -ForegroundColor Green
} else {
Write-Warning "CopyQ setup finished with exit code $LASTEXITCODE. It might be already installed or cancelled."
}
winget install --id "hluk.CopyQ" -e --source winget --accept-package-agreements --accept-source-agreements
# Write-Host "CopyQ check/install completed successfully." -ForegroundColor Green
# --- NEU: Konfiguration des Hotkeys ---
Write-Host " -> Configuring Global Hotkey (Ctrl+Q)..."
# 1. Pfad suchen (Da PATH evtl. noch nicht aktuell ist)
$copyqExe = "copyq" # Fallback
if (Test-Path "C:\Program Files\CopyQ\copyq.exe") {
$copyqExe = "C:\Program Files\CopyQ\copyq.exe"
} elseif (Test-Path "C:\Program Files (x86)\CopyQ\copyq.exe") {
$copyqExe = "C:\Program Files (x86)\CopyQ\copyq.exe"
}
# Mit Call-Operator (Achtung: Pfad muss quoted werden)
& "$copyqExe" config global_shortcuts/show "Ctrl+1"
if ($LASTEXITCODE -eq 0) { "OK" } else {
"Error: $LASTEXITCODE"
# Source - https://stackoverflow.com/a/22362868
# Posted by Michael Sorens
# Retrieved 2026-01-22, License - CC BY-SA 3.0
# Read-Host -Prompt "Press Enter to continue"
Start-Sleep -Seconds 2
}
# setup/install_ahk_copyq.ps1:76
#& ".\.venv\Scripts\python.exe" "tools\map_tagger.py" "--yes"
# Ensure relative to the installer script directory
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$pythonPath = Join-Path $scriptDir '..\.venv\Scripts\python.exe'
$pythonPath = (Resolve-Path $pythonPath).Path
# setup/install_ahk_copyq.ps1:87
Write-Host "Running map_tagger.py..."
$mapTagger = Join-Path $scriptDir '..\tools\map_tagger.py'
$mapTagger = (Resolve-Path $mapTagger).Path
& $pythonPath $mapTagger --yes
if ($LASTEXITCODE -eq 0) {
"OK"
} else {
"Error: $LASTEXITCODE"
Read-Host -Prompt "Press Enter to continue"
}
Write-Host "Running export_to_copyq.py..."
Start-Process "py" -ArgumentList "-3", "..\tools\export_to_copyq.py" -WorkingDirectory (Get-Location) -Wait -PassThru
if ($LASTEXITCODE -eq 0) { "OK" } else {
"Error: $LASTEXITCODE"
Read-Host -Prompt "Press Enter to continue"
}
# Source - https://stackoverflow.com/a/22362868
# Posted by Michael Sorens
# Retrieved 2026-01-22, License - CC BY-SA 3.0
# Read-Host -Prompt "Press Enter to continue"
# map_tagger.py --yes
#& ".\.venv\Scripts\python.exe" "tools\export_to_copyq.py"
# --- Notepad++ ---
# if exist "C:\Program Files\Notepad++\notepad++.exe" (
if (Test-Path "C:\Program Files\Notepad++\notepad++.exe")
{
echo [OK] Notepad++ ist schon da.
} else
{
echo [..] Installiere Notepad++...
winget install -e --id Notepad++.Notepad++ --silent --accept-source-agreements --accept-package-agreements
}
# --- Double Commander ---
if (Test-Path "C:\Program Files\Double Commander\doublecmd.exe")
{
echo [OK] Double Commander ist schon da.
} else
{
echo [..] Installiere Double Commander...
winget install -e --id DoubleCommander.DoubleCommander --silent --accept-source-agreements --accept-package-agreements
}
Write-Host "`nClient tools setup sequence finished." -ForegroundColor Cyan
setup/macos_setup.sh¶
#!/bin/bash
#
# setup/macos_setup.sh
# Run this setup script from the project's root directory.
#
SCRIPT_NAME=$(basename "$0")
# Check if the script is run from the project root.
# This check is more robust than changing directory.
if [ ! -f "requirements.txt" ]; then
echo "ERROR: Please run this script from the project's root directory."
echo ""
echo "cd .. ; ./setup/$SCRIPT_NAME"
exit 1
fi
should_remove_zips_after_unpack=true
# --- Make script location-independent ---
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJECT_ROOT=$(dirname "$SCRIPT_DIR")
cd "$PROJECT_ROOT"
echo "--> Running setup from project root: $(pwd)"
set -e
echo "--- Starting STT Setup for macOS ---"
# setup/macos_setup.sh
# --- 1. System Dependencies ---
if ! command -v brew &> /dev/null; then
echo "ERROR: Homebrew not found. Please install it from https://brew.sh"
exit 1
fi
echo "--> Checking for a compatible Java version (>=17)..."
JAVA_OK=0
if command -v java &> /dev/null; then
VERSION=$(java -version 2>&1 | awk -F'[."]' '/version/ {print $2}')
if [ "$VERSION" -ge 17 ]; then
echo " -> Found compatible Java version $VERSION. OK."
JAVA_OK=1
else
echo " -> Found Java version $VERSION, but we need >=17."
fi
else
echo " -> No Java executable found."
fi
if [ "$JAVA_OK" -eq 0 ]; then
echo " -> Installing a modern JDK via Homebrew..."
brew install openjdk
fi
echo "--> Installing other core dependencies..."
brew install fswatch wget unzip portaudio
# --- 2. Python Virtual Environment ---
# We check if the venv directory exists before creating it.
if [ ! -d ".venv" ]; then
echo "--> Creating Python virtual environment in './.venv'..."
python3 -m venv .venv
else
echo "--> Virtual environment already exists. Skipping creation."
fi
# --- 3. Python Requirements ---
echo "--> Preparing requirements for macOS..."
# The macOS equivalent, 'fswatch', is already installed via Homebrew.
sed -i.bak '/inotify-tools/d' requirements.txt
echo "--> Installing Python requirements into the virtual environment..."
if ! ./.venv/bin/pip install -r requirements.txt; then
echo "ERROR: Failed to install requirements. Trying to fix other common version issues..."
# Example: Fix vosk version, then retry
sed -i.bak 's/vosk==0.3.45/vosk/' requirements.txt
# We run the command again after the potential fixes
./.venv/bin/pip install -r requirements.txt
fi
# --- 4. Project Structure and Configuration ---
echo "--> Setting up project directories and initial files..."
python3 "scripts/py/func/create_required_folders.py" "$(pwd)"
# ==============================================================================
# --- 5. Download and Extract Required Components ---
# This block intelligently handles downloads and extractions.
# ==============================================================================
echo "--> Checking for required components (LanguageTool, Vosk-Models)..."
# --- Configuration ---
PREFIX="Z_"
# Format: "BaseName FinalDirName DestinationPath"
ARCHIVE_CONFIG=(
"LanguageTool-6.6 LanguageTool-6.6 ."
"vosk-model-en-us-0.22 vosk-model-en-us-0.22 ./models"
"vosk-model-small-en-us-0.15 vosk-model-small-en-us-0.15 ./models"
"vosk-model-de-0.21 vosk-model-de-0.21 ./models"
"lid.176 lid.176.bin ./models"
)
DOWNLOAD_REQUIRED=false
# --- Phase 1: Check and attempt to restore from local ZIP cache ---
echo " -> Phase 1: Checking and trying to restore from local cache..."
for config_line in "${ARCHIVE_CONFIG[@]}"; do
read -r base_name final_name dest_path <<< "$config_line"
target_path="$dest_path/$final_name"
zip_file="$PROJECT_ROOT/${PREFIX}${base_name}.zip"
# If the component already exists, we're good for this one.
if [ -e "$target_path" ]; then
continue
fi
# The component is missing. Let's see if we can unzip it from a local cache.
echo " -> Missing: '$target_path'. Searching for '$zip_file'..."
if [ -f "$zip_file" ]; then
echo " -> Found ZIP cache. Extracting '$zip_file'..."
unzip -q "$zip_file" -d "$dest_path"
else
# The ZIP is not there. We MUST run the downloader.
echo " -> ZIP cache not found. A download is required."
DOWNLOAD_REQUIRED=true
fi
done
# --- Phase 2: Download if necessary ---
if [ "$DOWNLOAD_REQUIRED" = true ]; then
echo " -> Phase 2: Running Python downloader for missing components..."
# Create the models directory before attempting to download files into it.
mkdir -p ./models
./.venv/bin/python tools/download_all_packages.py
echo " -> Downloader finished. Retrying extraction..."
# After downloading, we must re-check and extract anything that's still missing.
for config_line in "${ARCHIVE_CONFIG[@]}"; do
read -r base_name final_name dest_path <<< "$config_line"
target_path="$dest_path/$final_name"
zip_file="$PROJECT_ROOT/${PREFIX}${base_name}.zip"
if [ -e "$target_path" ]; then
continue
fi
if [ -f "$zip_file" ]; then
echo " -> Extracting newly downloaded '$zip_file'..."
unzip -q "$zip_file" -d "$dest_path"
else
echo " -> FATAL: Downloader ran but '$zip_file' is still missing. Aborting."
exit 1
fi
done
fi
echo "--> All components are present and correctly placed."
# ==============================================================================
# --- End of Download/Extract block ---
# ==============================================================================
# --- Install fzf (Fuzzy Finder) ---
if ! command -v fzf &> /dev/null; then
echo "[INFO] fzf not found. Installing..."
# Prüfen, ob brew installiert ist
if command -v brew &> /dev/null; then
brew install fzf
else
echo "[ERROR] Homebrew not found. Please install Homebrew first."
fi
else
echo "[INFO] fzf is already installed."
fi
source "$(dirname "${BASH_SOURCE[0]}")/../scripts/sh/get_lang.sh"
# --- 6. Completion ---
echo ""
echo "--- Setup for macOS is complete! ---"
echo ""
echo "IMPORTANT NEXT STEPS:"
echo ""
echo "1. Configure Java PATH:"
echo " To make Java available, you may need to add it to your shell's PATH."
echo " Run this command in your terminal:"
echo ' export PATH="$(brew --prefix openjdk@21)/bin:$PATH"'
echo " (Consider adding this line to your ~/.zshrc or ~/.bash_profile file to make it permanent)."
echo ""
echo "2. Activate Environment and Run:"
echo " To start the application, use the following commands:"
echo " source .venv/bin/activate"
echo " ./scripts/restart_venv_and_run-server.sh"
echo ""
echo "3. Potential macOS Permissions:"
echo " The 'xdotool' utility may require you to grant Accessibility permissions"
echo " to your Terminal app in 'System Settings -> Privacy & Security -> Accessibility'."
echo ""
setup/manjaro_arch_setup.sh¶
#!/bin/bash
#
# setup/manjaro_arch_setup.sh
# Run this setup script from the project's root directory.
#
SCRIPT_NAME=$(basename "$0")
# Check if the script is run from the project root.
# This check is more robust than changing directory.
if [ ! -f "requirements.txt" ]; then
echo "ERROR: Please run this script from the project's root directory."
echo ""
echo "cd .. ; ./setup/$SCRIPT_NAME"
exit 1
fi
# --- Argument Parsing for Exclusion ---
EXCLUDE_LANGUAGES=""
# --- Argument Parsing for Exclusion ---
EXCLUDE_LANGUAGES=""
# Parst Argumente in den Formaten: exclude=all, exclude=de, exclude=en, exclude=de,en
for arg in "$@"; do
# Prüft auf Formate: exclude=all, exclude=de, exclude=de,en (fängt alle Spracodes ab)
if [[ "$arg" =~ ^exclude=([a-zA-Z,]+)$ ]]; then
EXCLUDE_LANGUAGES="${BASH_REMATCH[1]}"
# Optional: Altes Format exclude:de,en beibehalten
elif [[ "$arg" =~ ^exclude:([a-zA-Z,]+)$ ]]; then
EXCLUDE_LANGUAGES="${BASH_REMATCH[1]}"
fi
done
if [ -n "$EXCLUDE_LANGUAGES" ]; then
echo "--> Exclusion list detected: $EXCLUDE_LANGUAGES"
fi
should_remove_zips_after_unpack=true
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJECT_ROOT=$(dirname "$SCRIPT_DIR")
cd "$PROJECT_ROOT"
echo "--> Running setup from project root: $(pwd)"
# --- End of location-independent block ---
set -e
echo "--- Starting STT Setup for Manjaro/Arch Linux ---"
# setup/manjaro_arch_setup.sh
# --- 1. System Dependencies ---
echo "--> Checking for a compatible Java version (>=17)..."
JAVA_OK=0
if command -v java &> /dev/null; then
VERSION=$(java -version 2>&1 | awk -F[\".] '/version/ {print ($2 == "1") ? $3 : $2}')
if [ "$VERSION" -ge 17 ]; then
echo " -> Found compatible Java version $VERSION. OK."
JAVA_OK=1
else
echo " -> Found Java version $VERSION, but we need >=17."
fi
else
echo " -> No Java executable found."
fi
if [ "$JAVA_OK" -eq 0 ]; then
echo " -> Installing a modern JDK to satisfy the requirement..."
sudo pacman -S --noconfirm --needed jdk-openjdk
fi
echo "--> Installing other core dependencies..."
sudo pacman -S --noconfirm --needed \
inotify-tools wget unzip portaudio xdotool
# --- 2. Python Virtual Environment ---
# We check if the venv directory exists before creating it.
if [ ! -d ".venv" ]; then
echo "--> Creating Python virtual environment in './.venv'..."
python3 -m venv .venv
else
echo "--> Virtual environment already exists. Skipping creation."
fi
# --- 3. Python Requirements ---
# We call pip from the venv directly. This is more robust than sourcing 'activate'.
echo "--> Installing Python requirements into the virtual environment..."
./.venv/bin/pip install -r requirements.txt
# --- 4. Project Structure and Configuration ---
echo "--> Setting up project directories and initial files..."
# THIS IS THE KEY CHANGE. We call the Python script and pass the current
# working directory (which is the project root) as an argument.
# This one command replaces all old 'mkdir' and 'touch' commands for the project structure.
python3 "scripts/py/func/create_required_folders.py" "$(pwd)"
# ==============================================================================
# --- 5. Download and Extract Required Components ---
# This block intelligently handles downloads and extractions.
# ==============================================================================
echo "--> Checking for required components (LanguageTool, Vosk-Models)..."
# --- Configuration ---
PREFIX="Z_"
# Format: "BaseName FinalDirName DestinationPath"
ARCHIVE_CONFIG=(
"LanguageTool-6.6 LanguageTool-6.6 ."
"vosk-model-en-us-0.22 vosk-model-en-us-0.22 ./models"
"vosk-model-small-en-us-0.15 vosk-model-small-en-us-0.15 ./models"
"vosk-model-de-0.21 vosk-model-de-0.21 ./models"
"lid.176 lid.176.bin ./models"
)
DOWNLOAD_REQUIRED=false
# --- Filter Configuration based on EXCLUDE_LANGUAGES ---
INSTALL_CONFIG=()
if [ -z "$EXCLUDE_LANGUAGES" ]; then
# Keine Ausschlüsse, die gesamte MASTER-Liste wird installiert.
INSTALL_CONFIG=("${ARCHIVE_CONFIG[@]}")
else
# Ausschlüsse aktiv, die Liste wird gefiltert.
for config_line in "${ARCHIVE_CONFIG[@]}"; do
read -r base_name final_name dest_path <<< "$config_line"
IS_MANDATORY=false
IS_EXCLUDED=false
# Komponenten, die immer benötigt werden (z.B. LanguageTool Core)
if [[ "$base_name" == "LanguageTool-6.6" ]] || [[ "$base_name" == "lid.176" ]]; then
IS_MANDATORY=true
fi
# 1. Ausschluss-Check: exclude=all
if [ "$EXCLUDE_LANGUAGES" == "all" ] && [ "$IS_MANDATORY" = false ]; then
echo " -> Excluding (all): $base_name"
IS_EXCLUDED=true
fi
# 2. Ausschluss-Check: Spezifische Sprachen (z.B. de, en)
if [ "$IS_EXCLUDED" = false ]; then
# Test auf 'de' im Namen und in der Ausschlussliste
if [[ "$base_name" =~ vosk-model-de- ]] && [[ "$EXCLUDE_LANGUAGES" =~ de ]]; then
echo " -> Excluding (de): $base_name"
IS_EXCLUDED=true
fi
# Test auf 'en' im Namen und in der Ausschlussliste
if [[ "$base_name" =~ vosk-model.*en-us ]] && [[ "$EXCLUDE_LANGUAGES" =~ en ]]; then
echo " -> Excluding (en): $base_name"
IS_EXCLUDED=true
fi
# Hinzufügen weiterer spezifischer Exklusionsregeln nach Bedarf...
fi
# Nur hinzufügen, wenn nicht ausgeschlossen
if [ "$IS_EXCLUDED" = false ]; then
INSTALL_CONFIG+=("$config_line")
fi
done
fi
# --- End Filter Configuration ---
# --- Phase 1: Check and attempt to restore from local ZIP cache ---
echo " -> Phase 1: Checking and trying to restore from local cache..."
for config_line in "${INSTALL_CONFIG[@]}"; do
read -r base_name final_name dest_path <<< "$config_line"
target_path="$dest_path/$final_name"
zip_file="$PROJECT_ROOT/${PREFIX}${base_name}.zip"
# If the component already exists, we're good for this one.
if [ -e "$target_path" ]; then
continue
fi
# The component is missing. Let's see if we can unzip it from a local cache.
echo " -> Missing: '$target_path'. Searching for '$zip_file'..."
if [ -f "$zip_file" ]; then
echo " -> Found ZIP cache. Extracting '$zip_file'..."
unzip -q "$zip_file" -d "$dest_path"
else
# The ZIP is not there. We MUST run the downloader.
echo " -> ZIP cache not found. A download is required."
DOWNLOAD_REQUIRED=true
fi
done
# --- Phase 2: Download if necessary ---
if [ "$DOWNLOAD_REQUIRED" = true ]; then
echo " -> Phase 2: Running Python downloader for missing components..."
# Create the models directory before attempting to download files into it.
mkdir -p ./models
./.venv/bin/python tools/download_all_packages.py --exclude "$EXCLUDE_LANGUAGES"
echo " -> Downloader finished. Retrying extraction..."
# After downloading, we must re-check and extract anything that's still missing.
for config_line in "${INSTALL_CONFIG[@]}"; do
read -r base_name final_name dest_path <<< "$config_line"
target_path="$dest_path/$final_name"
zip_file="$PROJECT_ROOT/${PREFIX}${base_name}.zip"
if [ -e "$target_path" ]; then
continue
fi
if [ -f "$zip_file" ]; then
echo " -> Extracting newly downloaded '$zip_file'..."
unzip -q "$zip_file" -d "$dest_path"
else
echo " -> FATAL: Downloader ran but '$zip_file' is still missing. Aborting."
exit 1
fi
done
fi
echo "--> All components are present and correctly placed."
# ==============================================================================
# --- End of Download/Extract block ---
# ==============================================================================
# Function to extract and clean up
expand_and_cleanup() {
local zip_file=$1
local expected_dir=$2
local dest_path=$3
# Check if final directory already exists
if [ -d "$dest_path/$expected_dir" ]; then
echo " -> Directory '$expected_dir' already exists. Skipping."
return
fi
# Check if the downloaded zip exists
if [ ! -f "$zip_file" ]; then
echo " -> FATAL: Expected archive not found: '$zip_file'"
exit 1
fi
echo " -> Extracting $zip_file to $dest_path..."
unzip -q "$zip_file" -d "$dest_path"
# Clean up the zip file
if [ "$should_remove_zips_after_unpack" = true ] ; then
rm "$zip_file"
fi
echo " -> Cleaned up ZIP file: $zip_file"
}
# Execute extraction for each archive
for config_line in "${BASE_CONFIG[@]}"; do
# Read the space-separated values into variables
read -r base_name final_name dest_path <<< "$config_line"
# CONSTRUCT THE FILENAMES, including the prefix for the zip file
zip_file="${PREFIX}${base_name}.zip"
expected_dir="${base_name}" # The final directory name has no prefix
if [ ! -e "$dest_path/$final_name" ]; then
echo " -> MISSING: '$dest_path/$final_name'. Download is required."
download_needed=true
break # Ein fehlendes Teil reicht, Prüfung kann stoppen
fi
expand_and_cleanup "$zip_file" "$expected_dir" "$dest_path"
done
echo " -> Extraction and cleanup successful."
# --- Install fzf (Fuzzy Finder) ---
if ! command -v fzf &> /dev/null; then
echo "[INFO] fzf not found. Installing..."
sudo pacman -S --noconfirm fzf
else
echo "[INFO] fzf is already installed."
fi
source "$(dirname "${BASH_SOURCE[0]}")/../scripts/sh/get_lang.sh"
# --- 5. Project Configuration ---
# Ensures Python can treat 'config' directories as packages.
echo "--> Creating Python package markers (__init__.py)..."
touch config/__init__.py
touch config/languagetool_server/__init__.py
# --- User-Specific Configuration ---
# This part is about user config, so it's fine for it to stay here.
CONFIG_FILE="$HOME/.config/sl5-stt/config.toml"
mkdir -p "$(dirname "$CONFIG_FILE")"
# Only write the file if it doesn't exist to avoid overwriting user settings
if [ ! -f "$CONFIG_FILE" ]; then
echo "[paths]" > "$CONFIG_FILE"
echo "project_root = \"$(pwd)\"" >> "$CONFIG_FILE"
fi
# --- 6. Completion ---
echo ""
echo "--- Setup for Manjaro/Arch is complete! ---"
echo ""
echo "To activate the environment and run the server, use the following commands:"
echo " source .venv/bin/activate"
echo " ./scripts/restart_venv_and_run-server.sh"
echo ""
setup/suse_setup.sh¶
#!/bin/bash
# setup/suse_setup.sh
# Run this setup script from the project's root directory.
SCRIPT_NAME=$(basename "$0")
# Check if the script is run from the project root.
# This check is more robust than changing directory.
if [ ! -f "requirements.txt" ]; then
echo "ERROR: Please run this script from the project's root directory."
echo ""
echo "cd .. ; ./setup/$SCRIPT_NAME"
exit 1
fi
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJECT_ROOT=$(dirname "$SCRIPT_DIR")
#cd "$PROJECT_ROOT"
# --- Argument Parsing for Exclusion ---
EXCLUDE_LANGUAGES=""
# --- Argument Parsing for Exclusion ---
EXCLUDE_LANGUAGES=""
# Parst Argumente in den Formaten: exclude=all, exclude=de, exclude=en, exclude=de,en
for arg in "$@"; do
# Prüft auf Formate: exclude=all, exclude=de, exclude=de,en (fängt alle Spracodes ab)
if [[ "$arg" =~ ^exclude=([a-zA-Z,]+)$ ]]; then
EXCLUDE_LANGUAGES="${BASH_REMATCH[1]}"
# Optional: Altes Format exclude:de,en beibehalten
elif [[ "$arg" =~ ^exclude:([a-zA-Z,]+)$ ]]; then
EXCLUDE_LANGUAGES="${BASH_REMATCH[1]}"
fi
done
if [ -n "$EXCLUDE_LANGUAGES" ]; then
echo "--> Exclusion list detected: $EXCLUDE_LANGUAGES"
fi
should_remove_zips_after_unpack=true
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJECT_ROOT=$(dirname "$SCRIPT_DIR")
cd "$PROJECT_ROOT"
echo "--> Running setup from project root: $(pwd)"
# --- End of location-independent block ---
echo "--- Starting STT Setup for openSUSE ---"
# --- End of location-independent block ---
set -e
# --- 1. System Dependencies ---
# (This section remains unchanged)
echo "--> Checking for a compatible Java version (>=17)..."
JAVA_OK=0
if command -v java &> /dev/null; then
VERSION=$(java -version 2>&1 | awk -F[\".] '/version/ {print ($2 == "1") ? $3 : $2}')
if [ "$VERSION" -ge 17 ]; then
echo " -> Found compatible Java version $VERSION. OK."
JAVA_OK=1
else
echo " -> Found Java version $VERSION, but we need >=17."
fi
else
echo " -> No Java executable found."
fi
if [ "$JAVA_OK" -eq 0 ]; then
echo " -> Installing a modern JDK (>=17)..."
# SUSE CHANGE: Use zypper instead of apt
sudo zypper refresh && sudo zypper -n install java-21-openjdk
fi
echo "--> Installing other core dependencies..."
# SUSE CHANGE: Use zypper and adjust package names
sudo zypper -n install \
inotify-tools wget unzip portaudio-devel python3-pip
# --- 2. Python Virtual Environment ---
# (This section remains unchanged)
if [ ! -d ".venv" ]; then
echo "--> Creating Python virtual environment in './.venv'..."
python3 -m venv .venv
else
echo "--> Virtual environment already exists. Skipping creation."
fi
# --- 3. Python Requirements ---
# (This section remains unchanged)
echo "--> Installing Python requirements into the virtual environment..."
./.venv/bin/pip install -r requirements.txt
# --- 4. Project Structure and Configuration ---
echo "--> Setting up project directories and initial files..."
# THIS IS THE KEY CHANGE. We call the Python script and pass the current
# working directory (which is the project root) as an argument.
# This one command replaces all old 'mkdir' and 'touch' commands for the project structure.
python3 "scripts/py/func/create_required_folders.py" "$(pwd)"
# ==============================================================================
# --- 5. Download and Extract Required Components ---
# This block intelligently handles downloads and extractions.
# ==============================================================================
echo "--> Checking for required components (LanguageTool, Vosk-Models)..."
# --- Configuration ---
PREFIX="Z_"
# Format: "BaseName FinalDirName DestinationPath"
ARCHIVE_CONFIG=(
"LanguageTool-6.6 LanguageTool-6.6 ."
"vosk-model-en-us-0.22 vosk-model-en-us-0.22 ./models"
"vosk-model-small-en-us-0.15 vosk-model-small-en-us-0.15 ./models"
"vosk-model-de-0.21 vosk-model-de-0.21 ./models"
"lid.176 lid.176.bin ./models"
)
DOWNLOAD_REQUIRED=false
# --- Filter Configuration based on EXCLUDE_LANGUAGES ---
INSTALL_CONFIG=()
if [ -z "$EXCLUDE_LANGUAGES" ]; then
# Keine Ausschlüsse, die gesamte MASTER-Liste wird installiert.
INSTALL_CONFIG=("${ARCHIVE_CONFIG[@]}")
else
# Ausschlüsse aktiv, die Liste wird gefiltert.
for config_line in "${ARCHIVE_CONFIG[@]}"; do
read -r base_name final_name dest_path <<< "$config_line"
IS_MANDATORY=false
IS_EXCLUDED=false
# Komponenten, die immer benötigt werden (z.B. LanguageTool Core)
if [[ "$base_name" == "LanguageTool-6.6" ]] || [[ "$base_name" == "lid.176" ]]; then
IS_MANDATORY=true
fi
# 1. Ausschluss-Check: exclude=all
if [ "$EXCLUDE_LANGUAGES" == "all" ] && [ "$IS_MANDATORY" = false ]; then
echo " -> Excluding (all): $base_name"
IS_EXCLUDED=true
fi
# 2. Ausschluss-Check: Spezifische Sprachen (z.B. de, en)
if [ "$IS_EXCLUDED" = false ]; then
# Test auf 'de' im Namen und in der Ausschlussliste
if [[ "$base_name" =~ vosk-model-de- ]] && [[ "$EXCLUDE_LANGUAGES" =~ de ]]; then
echo " -> Excluding (de): $base_name"
IS_EXCLUDED=true
fi
# Test auf 'en' im Namen und in der Ausschlussliste
if [[ "$base_name" =~ vosk-model.*en-us ]] && [[ "$EXCLUDE_LANGUAGES" =~ en ]]; then
echo " -> Excluding (en): $base_name"
IS_EXCLUDED=true
fi
# Hinzufügen weiterer spezifischer Exklusionsregeln nach Bedarf...
fi
# Nur hinzufügen, wenn nicht ausgeschlossen
if [ "$IS_EXCLUDED" = false ]; then
INSTALL_CONFIG+=("$config_line")
fi
done
fi
# --- End Filter Configuration ---
# --- Phase 1: Check and attempt to restore from local ZIP cache ---
echo " -> Phase 1: Checking and trying to restore from local cache..."
for config_line in "${INSTALL_CONFIG[@]}"; do
read -r base_name final_name dest_path <<< "$config_line"
target_path="$dest_path/$final_name"
zip_file="$PROJECT_ROOT/${PREFIX}${base_name}.zip"
# If the component already exists, we're good for this one.
if [ -e "$target_path" ]; then
continue
fi
# The component is missing. Let's see if we can unzip it from a local cache.
echo " -> Missing: '$target_path'. Searching for '$zip_file'..."
if [ -f "$zip_file" ]; then
echo " -> Found ZIP cache. Extracting '$zip_file'..."
unzip -q "$zip_file" -d "$dest_path"
else
# The ZIP is not there. We MUST run the downloader.
echo " -> ZIP cache not found. A download is required."
DOWNLOAD_REQUIRED=true
fi
done
# --- Phase 2: Download if necessary ---
if [ "$DOWNLOAD_REQUIRED" = true ]; then
echo " -> Phase 2: Running Python downloader for missing components..."
# Create the models directory before attempting to download files into it.
mkdir -p ./models
./.venv/bin/python tools/download_all_packages.py --exclude "$EXCLUDE_LANGUAGES"
echo " -> Downloader finished. Retrying extraction..."
# After downloading, we must re-check and extract anything that's still missing.
for config_line in "${INSTALL_CONFIG[@]}"; do
read -r base_name final_name dest_path <<< "$config_line"
target_path="$dest_path/$final_name"
zip_file="$PROJECT_ROOT/${PREFIX}${base_name}.zip"
if [ -e "$target_path" ]; then
continue
fi
if [ -f "$zip_file" ]; then
echo " -> Extracting newly downloaded '$zip_file'..."
unzip -q "$zip_file" -d "$dest_path"
else
echo " -> FATAL: Downloader ran but '$zip_file' is still missing. Aborting."
exit 1
fi
done
fi
echo "--> All components are present and correctly placed."
# ==============================================================================
# --- End of Download/Extract block ---
# ==============================================================================
# Function to extract and clean up
expand_and_cleanup() {
local zip_file=$1
local expected_dir=$2
local dest_path=$3
# Check if final directory already exists
if [ -d "$dest_path/$expected_dir" ]; then
echo " -> Directory '$expected_dir' already exists. Skipping."
return
fi
# Check if the downloaded zip exists
if [ ! -f "$zip_file" ]; then
echo " -> FATAL: Expected archive not found: '$zip_file'"
exit 1
fi
echo " -> Extracting $zip_file to $dest_path..."
unzip -q "$zip_file" -d "$dest_path"
# Clean up the zip file
if [ "$should_remove_zips_after_unpack" = true ] ; then
rm "$zip_file"
fi
echo " -> Cleaned up ZIP file: $zip_file"
}
# Execute extraction for each archive
for config_line in "${BASE_CONFIG[@]}"; do
# Read the space-separated values into variables
read -r base_name final_name dest_path <<< "$config_line"
# CONSTRUCT THE FILENAMES, including the prefix for the zip file
zip_file="${PREFIX}${base_name}.zip"
expected_dir="${base_name}" # The final directory name has no prefix
if [ ! -e "$dest_path/$final_name" ]; then
echo " -> MISSING: '$dest_path/$final_name'. Download is required."
download_needed=true
break # Ein fehlendes Teil reicht, Prüfung kann stoppen
fi
expand_and_cleanup "$zip_file" "$expected_dir" "$dest_path"
done
echo " -> Extraction and cleanup successful."
# --- Install fzf (Fuzzy Finder) ---
if ! command -v fzf &> /dev/null; then
echo "[INFO] fzf not found. Installing..."
sudo zypper install -y fzf
else
echo "[INFO] fzf is already installed."
fi
source "$(dirname "${BASH_SOURCE[0]}")/../scripts/sh/get_lang.sh"
# --- 5. Project Configuration ---
# Ensures Python can treat 'config' directories as packages.
echo '--> Creating Python package markers (__init__.py)...'
touch config/__init__.py
touch config/languagetool_server/__init__.py
# --- User-Specific Configuration ---
# This part is about user config, so it's fine for it to stay here.
CONFIG_FILE="$HOME/.config/sl5-stt/config.toml"
echo "--> Ensuring user config file exists at $CONFIG_FILE..."
mkdir -p "$(dirname "$CONFIG_FILE")"
# Only write the file if it doesn't exist to avoid overwriting user settings
if [ ! -f "$CONFIG_FILE" ]; then
echo "[paths]" > "$CONFIG_FILE"
echo "project_root = \"$(pwd)\"" >> "$CONFIG_FILE"
fi
# --- 6. Completion ---
echo ""
echo "--- Setup for openSUSE is complete! ---"
echo ""
echo "To activate the environment and run the server, use the following commands:"
echo " source .venv/bin/activate"
echo " ./scripts/restart_venv_and_run-server.sh"
echo ""
setup/ubuntu_setup.sh¶
#!/bin/bash
#
# setup/ubuntu_setup.sh
# Run this setup script from the project's root directory.
#
SCRIPT_NAME=$(basename "$0")
# Check if the script is run from the project root.
# This check is more robust than changing directory.
if [ ! -f "requirements.txt" ]; then
echo "ERROR: Please run this script from the project's root directory."
echo ""
echo "cd .. ; ./setup/$SCRIPT_NAME"
exit 1
fi
# --- Argument Parsing for Exclusion ---
EXCLUDE_LANGUAGES=""
# Parst Argumente wie 'exclude:de,en' oder 'exclude=all'
for arg in "$@"; do
if [[ "$arg" =~ ^exclude:([a-zA-Z,]+)$ ]]; then
EXCLUDE_LANGUAGES="${BASH_REMATCH[1]}"
elif [[ "$arg" == "exclude=all" ]]; then
EXCLUDE_LANGUAGES="all"
fi
done
if [ -n "$EXCLUDE_LANGUAGES" ]; then
echo "--> Exclusion list detected: $EXCLUDE_LANGUAGES"
fi
# --- End Argument Parsing ---
should_remove_zips_after_unpack=true
# --- Make script location-independent ---
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJECT_ROOT=$(dirname "$SCRIPT_DIR")
cd "$PROJECT_ROOT"
echo "--> Running setup from project root: $(pwd)"
# --- End of location-independent block ---
set -e
echo "--- Starting STT Setup for Debian/Ubuntu ---"
sudo apt-get update -y
# setup/ubuntu_setup.sh
# --- 1. System Dependencies ---
# (This section remains unchanged)
echo "--> Checking for a compatible Java version (>=17)..."
JAVA_OK=0
if command -v java &> /dev/null; then
VERSION=$(java -version 2>&1 | awk -F[\".] '/version/ {print ($2 == "1") ? $3 : $2}')
if [ "$VERSION" -ge 17 ]; then
echo " -> Found compatible Java version $VERSION. OK."
JAVA_OK=1
else
echo " -> Found Java version $VERSION, but we need >=17."
fi
else
echo " -> No Java executable found."
fi
if [ "$JAVA_OK" -eq 0 ]; then
echo " -> Installing a modern JDK (>=17)..."
sudo apt-get update && sudo apt-get install -y openjdk-21-jdk
fi
echo "--> Installing other core dependencies..."
sudo apt-get install -y \
inotify-tools wget unzip portaudio19-dev python3-pip \
ffmpeg libnotify-bin xclip xvfb
# --- 2. Python Virtual Environment ---
# (This section remains unchanged)
if [ ! -d ".venv" ]; then
echo "--> Creating Python virtual environment in './.venv'..."
python3 -m venv .venv
else
echo "--> Virtual environment already exists. Skipping creation."
fi
# --- 3. Python Requirements ---
# (This section remains unchanged)
echo "--> Installing Python requirements into the virtual environment..."
./.venv/bin/pip install -r requirements.txt
# --- 4. Project Structure and Configuration ---
echo "--> Setting up project directories and initial files..."
# THIS IS THE KEY CHANGE. We call the Python script and pass the current
# working directory (which is the project root) as an argument.
# This one command replaces all old 'mkdir' and 'touch' commands for the project structure.
python3 "scripts/py/func/create_required_folders.py" "$(pwd)"
# ==============================================================================
# --- 5. Download and Extract Required Components ---
# This block intelligently handles downloads and extractions.
# ==============================================================================
echo "--> Checking for required components (LanguageTool, Vosk-Models)..."
# --- Configuration ---
PREFIX="Z_"
# Format: "BaseName FinalDirName DestinationPath"
ARCHIVE_CONFIG=(
"LanguageTool-6.6 LanguageTool-6.6 ."
"vosk-model-en-us-0.22 vosk-model-en-us-0.22 ./models"
"vosk-model-small-en-us-0.15 vosk-model-small-en-us-0.15 ./models"
"vosk-model-de-0.21 vosk-model-de-0.21 ./models"
"lid.176 lid.176.bin ./models"
)
DOWNLOAD_REQUIRED=false
# --- Filter Configuration based on EXCLUDE_LANGUAGES ---
INSTALL_CONFIG=()
if [ -z "$EXCLUDE_LANGUAGES" ]; then
# Keine Ausschlüsse, die gesamte MASTER-Liste wird installiert.
INSTALL_CONFIG=("${ARCHIVE_CONFIG[@]}")
else
# Ausschlüsse aktiv, die Liste wird gefiltert.
for config_line in "${ARCHIVE_CONFIG[@]}"; do
read -r base_name final_name dest_path <<< "$config_line"
IS_MANDATORY=false
IS_EXCLUDED=false
# Komponenten, die immer benötigt werden (z.B. LanguageTool Core)
if [[ "$base_name" == "LanguageTool-6.6" ]] || [[ "$base_name" == "lid.176" ]]; then
IS_MANDATORY=true
fi
# 1. Ausschluss-Check: exclude=all
if [ "$EXCLUDE_LANGUAGES" == "all" ] && [ "$IS_MANDATORY" = false ]; then
echo " -> Excluding (all): $base_name"
IS_EXCLUDED=true
fi
# 2. Ausschluss-Check: Spezifische Sprachen (z.B. de, en)
if [ "$IS_EXCLUDED" = false ]; then
# Test auf 'de' im Namen und in der Ausschlussliste
if [[ "$base_name" =~ vosk-model-de- ]] && [[ "$EXCLUDE_LANGUAGES" =~ de ]]; then
echo " -> Excluding (de): $base_name"
IS_EXCLUDED=true
fi
# Test auf 'en' im Namen und in der Ausschlussliste
if [[ "$base_name" =~ vosk-model-en-us- ]] && [[ "$EXCLUDE_LANGUAGES" =~ en ]]; then
echo " -> Excluding (en): $base_name"
IS_EXCLUDED=true
fi
# Hinzufügen weiterer spezifischer Exklusionsregeln nach Bedarf...
fi
# Nur hinzufügen, wenn nicht ausgeschlossen
if [ "$IS_EXCLUDED" = false ]; then
INSTALL_CONFIG+=("$config_line")
fi
done
fi
# --- End Filter Configuration ---
# --- Phase 1: Check and attempt to restore from local ZIP cache ---
echo " -> Phase 1: Checking and trying to restore from local cache..."
for config_line in "${INSTALL_CONFIG[@]}"; do
read -r base_name final_name dest_path <<< "$config_line"
target_path="$dest_path/$final_name"
zip_file="$PROJECT_ROOT/${PREFIX}${base_name}.zip"
# If the component already exists, we're good for this one.
if [ -e "$target_path" ]; then
continue
fi
# The component is missing. Let's see if we can unzip it from a local cache.
echo " -> Missing: '$target_path'. Searching for '$zip_file'..."
if [ -f "$zip_file" ]; then
echo " -> Found ZIP cache. Extracting '$zip_file'..."
unzip -q "$zip_file" -d "$dest_path"
else
# The ZIP is not there. We MUST run the downloader.
echo " -> ZIP cache not found. A download is required."
DOWNLOAD_REQUIRED=true
fi
done
# --- Phase 2: Download if necessary ---
if [ "$DOWNLOAD_REQUIRED" = true ]; then
echo " -> Phase 2: Running Python downloader for missing components..."
# Create the models directory before attempting to download files into it.
mkdir -p ./models
./.venv/bin/python tools/download_all_packages.py --exclude "$EXCLUDE_LANGUAGES"
echo " -> Downloader finished. Retrying extraction..."
# After downloading, we must re-check and extract anything that's still missing.
for config_line in "${INSTALL_CONFIG[@]}"; do
read -r base_name final_name dest_path <<< "$config_line"
target_path="$dest_path/$final_name"
zip_file="$PROJECT_ROOT/${PREFIX}${base_name}.zip"
if [ -e "$target_path" ]; then
continue
fi
if [ -f "$zip_file" ]; then
echo " -> Extracting newly downloaded '$zip_file'..."
unzip -q "$zip_file" -d "$dest_path"
else
echo " -> FATAL: Downloader ran but '$zip_file' is still missing. Aborting."
exit 1
fi
done
fi
echo "--> All components are present and correctly placed."
# ==============================================================================
# --- End of Download/Extract block ---
# ==============================================================================
# Function to extract and clean up
expand_and_cleanup() {
local zip_file=$1
local expected_dir=$2
local dest_path=$3
# Check if final directory already exists
if [ -d "$dest_path/$expected_dir" ]; then
echo " -> Directory '$expected_dir' already exists. Skipping."
return
fi
# Check if the downloaded zip exists
if [ ! -f "$zip_file" ]; then
echo " -> FATAL: Expected archive not found: '$zip_file'"
exit 1
fi
echo " -> Extracting $zip_file to $dest_path..."
unzip -q "$zip_file" -d "$dest_path"
# Clean up the zip file
if [ "$should_remove_zips_after_unpack" = true ] ; then
rm "$zip_file"
fi
echo " -> Cleaned up ZIP file: $zip_file"
}
# Execute extraction for each archive
for config_line in "${BASE_CONFIG[@]}"; do
# Read the space-separated values into variables
read -r base_name final_name dest_path <<< "$config_line"
# CONSTRUCT THE FILENAMES, including the prefix for the zip file
zip_file="${PREFIX}${base_name}.zip"
expected_dir="${base_name}" # The final directory name has no prefix
if [ ! -e "$dest_path/$final_name" ]; then
echo " -> MISSING: '$dest_path/$final_name'. Download is required."
download_needed=true
break # Ein fehlendes Teil reicht, Prüfung kann stoppen
fi
expand_and_cleanup "$zip_file" "$expected_dir" "$dest_path"
done
echo " -> Extraction and cleanup successful."
source "$(dirname "${BASH_SOURCE[0]}")/../scripts/sh/get_lang.sh"
# --- Install fzf (Fuzzy Finder) ---
if ! command -v fzf &> /dev/null; then
echo "[INFO] fzf not found. Installing..."
# We use apt for simplicity in the setup script
sudo apt-get update && sudo apt-get install -y fzf
# Optional: If you want the latest version with full shell bindings:
# git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
# ~/.fzf/install --all
else
echo "[INFO] fzf is already installed."
fi
# --- 5. Project Configuration ---
# Ensures Python can treat 'config' directories as packages.
echo "--> Creating Python package markers (__init__.py)..."
touch config/__init__.py
touch config/languagetool_server/__init__.py
# --- User-Specific Configuration ---
# This part is about user config, so it's fine for it to stay here.
CONFIG_FILE="$HOME/.config/sl5-stt/config.toml"
echo "--> Ensuring user config file exists at $CONFIG_FILE..."
mkdir -p "$(dirname "$CONFIG_FILE")"
# Only write the file if it doesn't exist to avoid overwriting user settings
if [ ! -f "$CONFIG_FILE" ]; then
echo "[paths]" > "$CONFIG_FILE"
echo "project_root = \"$(pwd)\"" >> "$CONFIG_FILE"
fi
# --- 6. Completion ---
echo ""
echo "--- Setup for Ubuntu is complete! ---"
echo ""
echo "To activate the environment and run the server, use the following commands:"
echo " source .venv/bin/activate"
echo " ./scripts/restart_venv_and_run-server.sh"
echo ""
setup/uninstall_ahk_copyq.bat¶
REM uninstall_ahk_copyq.bat
REM script_name: setup/uninstall_ahk_copyq.bat
@echo off
ECHO Starting Uninstallation of Client Tools (AutoHotkey + CopyQ)...
REM --- ADMIN CHECK START ---
REM Uninstallations via Winget/MSI usually require Admin privileges.
FSUTIL dirty query %systemdrive% >nul
IF %ERRORLEVEL% NEQ 0 (
ECHO Requesting Administrator privileges...
powershell -Command "Start-Process cmd -ArgumentList '/c \"\"%~dpnx0\"\" %* ' -Verb RunAs"
EXIT /B
)
ECHO [OK] Admin privileges confirmed.
REM --- ADMIN CHECK END ---
REM Ensure we are in the script directory to find the ps1 file
CD /D "%~dp0"
ECHO Running uninstallation script...
powershell.exe -ExecutionPolicy Bypass -File "%~dp0\uninstall_ahk_copyq.ps1"
ECHO.
ECHO Uninstallation process completed.
PAUSE
setup/uninstall_ahk_copyq.ps1¶
# uninstall_ahk_copyq.ps1
# script_name: setup/uninstall_ahk_copyq.ps1
Write-Host "Starting uninstallation of optional client tools..." -ForegroundColor Cyan
# Check if winget is available
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
Write-Error "Winget command not found. Cannot proceed with automated uninstallation."
exit 1
}
# --- STEP 1: Stop running processes ---
Write-Host "Stopping running instances of CopyQ and AutoHotkey..." -ForegroundColor Yellow
Stop-Process -Name "copyq" -Force -ErrorAction SilentlyContinue
Stop-Process -Name "AutoHotkey" -Force -ErrorAction SilentlyContinue
Stop-Process -Name "AutoHotkey64" -Force -ErrorAction SilentlyContinue
Stop-Process -Name "AutoHotkey32" -Force -ErrorAction SilentlyContinue
# --- STEP 2: Uninstall CopyQ ---
Write-Host "Attempting to uninstall CopyQ..." -ForegroundColor Yellow
# We check if it is installed to avoid unnecessary error messages
$copyqCheck = winget list --id "hluk.CopyQ"
if ($copyqCheck) {
winget uninstall --id "hluk.CopyQ" --silent --accept-source-agreements
if ($LASTEXITCODE -eq 0) {
Write-Host "CopyQ uninstalled successfully." -ForegroundColor Green
} else {
Write-Warning "CopyQ uninstallation finished with exit code $LASTEXITCODE."
}
} else {
Write-Host "CopyQ does not appear to be installed via Winget." -ForegroundColor DarkGray
}
# --- STEP 3: Uninstall AutoHotkey ---
Write-Host "Attempting to uninstall AutoHotkey..." -ForegroundColor Yellow
$ahkCheck = winget list --id "AutoHotkey.AutoHotkey"
if ($ahkCheck) {
winget uninstall --id "AutoHotkey.AutoHotkey" --silent --accept-source-agreements
if ($LASTEXITCODE -eq 0) {
Write-Host "AutoHotkey uninstalled successfully." -ForegroundColor Green
} else {
Write-Warning "AutoHotkey uninstallation finished with exit code $LASTEXITCODE."
}
} else {
Write-Host "AutoHotkey does not appear to be installed via Winget." -ForegroundColor DarkGray
}
Write-Host "Uninstallation sequence finished." -ForegroundColor Cyan
setup/windows11_setup.bat¶
REM windows11_setup.bat
@echo off
ECHO start Windows 11 Setup-Script...
REM PowerShell, use Execution Policy this time
REM %* passes all arguments (e.g., -Exclude "en") given to the BAT file to the PowerShell script.
powershell.exe -ExecutionPolicy Bypass -File "%~dp0\windows11_setup.ps1" %*
REM NOTE: The Python script call should ideally be inside the PowerShell script,
REM as the virtual environment (.\.venv) is only guaranteed to be set up *after* the PS1 script finishes.
REM Assuming the PS1 script now handles all necessary setup and model downloading/filtering.
call .\.venv\Scripts\python.exe scripts/py/func/setup_initial_model.py
ECHO
ECHO Script is ended.
ECHO You can close the window
REM pause
setup/windows11_setup.ps1¶
# setup/windows11_setup.ps1
# how to start:
# .\setup\windows11_setup.ps1 -Exclude "en" or .\setup\windows11_setup.ps1 -Exclude "de" or .\setup\windows11_setup.ps1 -Exclude "all".
# --- Argument Parsing for Exclusion (NEW) ---
param(
[string]$Exclude = $null
)
# --- Make script location-independent ---
$ProjectRoot = Split-Path -Path $PSScriptRoot -Parent
Set-Location -Path $ProjectRoot
Write-Host "--> Running setup from project root: $(Get-Location)"
$EXCLUDE_LANGUAGES = $Exclude
if (-not [string]::IsNullOrEmpty($EXCLUDE_LANGUAGES)) {
Write-Host "--> Exclusion list detected: $EXCLUDE_LANGUAGES" -ForegroundColor Yellow
} else {
Write-Host "No exclusion list provided."
}
# --- End Argument Parsing ---
$ErrorActionPreference = "Stop"
# Configuration: Set to $false to keep ZIP files after extraction or use $true
$should_remove_zips_after_unpack = $true
# --- 1. Admin Rights Check ---
Write-Host "[*] Checking for Administrator privileges"
# Only check for admin rights if NOT running in a CI environment (like GitHub Actions)
# $PSCommandPath is an automatic variable in PowerShell that contains the full path and filename
# of the script that is currently executing.
if ($env:CI -ne 'true') {
# Check if the current user is an Administrator
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Host "[ERROR] Administrator privileges are required. Re-launching..."
# Re-run the current script with elevated privileges for GitHub Actions
# Start-Process -FilePath $PSCommandPath -Verb RunAs
Start-Process -FilePath "powershell.exe" -ArgumentList @(
'-NoProfile',
'-ExecutionPolicy', 'Bypass',
'-File', "`"$PSCommandPath`"" # <--- Note the escaped quotes here
) -Verb RunAs
# Exit the current (non-admin) script
exit
}
}
Write-Host "[SUCCESS] Running with Administrator privileges."
<#
Java-Balken always show (2-3 Sekunden) – thats normal
Wenn der Balken für 160 MB in 2-3 Sekunden durchläuft, lädt Windows die Datei nicht wirklich herunter.
Was passiert da? winget prüft die vorhandene Installation und vergleicht den "Hash" (Fingerabdruck) der Datei.
Es merkt: "Datei ist schon da und korrekt", und setzt den Fortschritt sofort auf 100%.
Fazit: please ignore it. The Das System works.
22.1.'26 16:07 Thu
#>
# Only check for java if NOT running in a CI environment (like GitHub Actions)
if ($env:CI -ne 'true')
{
# --- 2. Java Installation Check ---
Write-Host "--> Checking Java installation..."
$JavaVersion = $null
try
{
$JavaOutput = & java -version 2>&1
if ($JavaOutput -match 'version "(\d+)\.')
{
$JavaVersion = [int]$matches[1]
}
elseif ($JavaOutput -match 'version "1\.(\d+)\.')
{
$JavaVersion = [int]$matches[1]
}
}
catch
{
Write-Host " -> Java not found in PATH."
}
# setup/windows11_setup.ps1:81
if ($JavaVersion -and $JavaVersion -ge 17)
{
Write-Host " -> Java $JavaVersion detected. OK." -ForegroundColor Green
}
else
{
Write-Host " -> Java 17+ ... OpenJDK 17..." -ForegroundColor Yellow
try
{
# setup/windows11_setup.ps1:91
winget install --id Microsoft.OpenJDK.17 --silent --accept-source-agreements --accept-package-agreements --force
if ($LASTEXITCODE -eq 0)
{
Write-Host " -> OpenJDK 17 installed successfully." -ForegroundColor Green
# Refresh PATH for current session
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User")
}
else
{
Write-Host "ERROR: Failed to install OpenJDK 17. Please install manually." -ForegroundColor Red
# exit 1 # the script works anywas usuallly. dont exit here!
}
}
catch
{
Write-Host "ERROR: Failed to install Java. Please install Java 17+ manually." -ForegroundColor Red
exit 1
}
}
}
# --- 3. Python Installation Check ---
Write-Host "--> Checking Python installation..."
$PythonVersion = $null
try {
$PythonOutput = & python --version 2>&1
if ($PythonOutput -match 'Python (\d+)\.(\d+)') {
$PythonMajor = [int]$matches[1]
$PythonMinor = [int]$matches[2]
if ($PythonMajor -eq 3 -and $PythonMinor -ge 8) {
$PythonVersion = "$PythonMajor.$PythonMinor"
}
}
} catch {
Write-Host " -> Python not found in PATH."
}
if ($PythonVersion) {
Write-Host " -> Python $PythonVersion detected. OK." -ForegroundColor Green
} else {
Write-Host " -> Python 3.8+ not found. Installing Python 3.11..." -ForegroundColor Yellow
try {
winget install --id Python.Python.3.11 --silent --accept-source-agreements --accept-package-agreements
if ($LASTEXITCODE -eq 0) {
Write-Host " -> Python 3.11 installed successfully." -ForegroundColor Green
# Refresh PATH for current session
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User")
} else {
Write-Host "ERROR: Failed to install Python. Please install manually." -ForegroundColor Red
exit 1
}
} catch {
Write-Host "ERROR: Failed to install Python. Please install Python 3.8+ manually." -ForegroundColor Red
exit 1
}
}
# --- 3. Python Virtual Environment ---
Write-Host "--> Creating Python virtual environment in '.\.venv'..."
if (-not (Test-Path -Path ".\.venv")) {
python -m venv .venv
} else {
Write-Host " -> Virtual environment already exists. Skipping creation."
}
# --- PATCH: Replace fasttext with fasttext-wheel in requirements.txt ---
Write-Host "--> Patching requirements.txt for Windows fasttext-wheel compatibility..."
(Get-Content requirements.txt) -replace '^fasttext.*$', 'fasttext-wheel' | Set-Content requirements.txt
# --- 5. Python Requirements ---
Write-Host "--> Installing Python requirements into the virtual environment..."
#.\.venv\Scripts\pip.exe install --upgrade pip # falsch innerhalbe eines setupa
python -m pip install --upgrade pip
#.\.venv\Scripts\pip.exe install -r requirements.txt
& ".\.venv\Scripts\python.exe" -m pip install -r requirements.txt
# --- 5. External Tools & Models (from Releases) ---
$LtDir = "LanguageTool-6.6"
# --- 6. External Tools & Models (using the robust Python downloader) ---
Write-Host "--> Downloading external tools and models via Python downloader..."
# Create the models directory before attempting to download files into it.
New-Item -ItemType Directory -Path ".\models" -Force | Out-Null
# setup/windows11_setup.ps1:183
# Execute the downloader and check its exit code
# It downloads all required ZIPs to the project root.
.\.venv\Scripts\python.exe tools/download_all_packages.py --exclude "$EXCLUDE_LANGUAGES"
# $LASTEXITCODE contains the exit code of the last program that was run.
# 0 means success. Anything else is an error.
if ($LASTEXITCODE -ne 0) {
Write-Host "FATAL: The Python download script failed. Halting setup." -ForegroundColor Red
# Wir benutzen 'exit 1', um das ganze Skript sofort zu beenden.
exit 1
}
Write-Host " -> Python downloader completed successfully." -ForegroundColor Green
# --- Now, extract the downloaded archives ---
Write-Host "--> Extracting downloaded archives..."
# setup/windows11_setup.ps1:207
# --- Configuration: Base List of Archives ---
$Prefix = "Z_"
$MasterConfig = @(
@{ BaseName = "LanguageTool-6.6"; FinalName = "LanguageTool-6.6"; Dest = "." },
@{ BaseName = "vosk-model-en-us-0.22"; FinalName = "vosk-model-en-us-0.22"; Dest = ".\models" },
@{ BaseName = "vosk-model-small-en-us-0.15"; FinalName = "vosk-model-small-en-us-0.15"; Dest = ".\models" },
@{ BaseName = "vosk-model-de-0.21"; FinalName = "vosk-model-de-0.21"; Dest = ".\models" },
@{ BaseName = "lid.176"; FinalName = "lid.176.bin"; Dest = ".\models" }
)
# --- Filter Configuration based on EXCLUDE_LANGUAGES (NEW) ---
$INSTALL_CONFIG = @()
$ExcludeList = @($EXCLUDE_LANGUAGES.Split(',') | ForEach-Object { $_.Trim().ToLower() })
if ($ExcludeList.Count -eq 0 -or $ExcludeList[0] -eq "") {
# No exclusion, use master list
$INSTALL_CONFIG = $MasterConfig
} else {
# Exclusion active, filter the list
foreach ($ConfigItem in $MasterConfig) {
$IsMandatory = $ConfigItem.BaseName -like "LanguageTool-*" -or $ConfigItem.BaseName -eq "lid.176"
$IsExcluded = $false
# 1. Exclusion Check: exclude=all
if ($ExcludeList -contains "all" -and -not $IsMandatory) {
Write-Host " -> Excluding (all): $($ConfigItem.BaseName)"
$IsExcluded = $true
}
# 2. Exclusion Check: Specific Languages
if (-not $IsExcluded) {
# Check for 'de' exclusion
if ($ExcludeList -contains "de" -and $ConfigItem.BaseName -like "*vosk-model-de-*") {
Write-Host " -> Excluding (de): $($ConfigItem.BaseName)"
$IsExcluded = $true
}
# Check for 'en' exclusion (covers both en-us models)
if ($ExcludeList -contains "en" -and $ConfigItem.BaseName -like "*vosk-model*en-us*") {
Write-Host " -> Excluding (en): $($ConfigItem.BaseName)"
$IsExcluded = $true
}
# Add other specific language checks here if needed...
}
# Only add if not excluded
if (-not $IsExcluded) {
$INSTALL_CONFIG += $ConfigItem
}
}
}
# Now, create the ArchiveConfig from the filtered INSTALL_CONFIG
$ArchiveConfig = $INSTALL_CONFIG | ForEach-Object {
[PSCustomObject]@{
Zip = "$($Prefix)$($_.BaseName).zip"
Dir = $_.BaseName
Dest = $_.Dest
}
}
# --- End Filter Configuration ---
# setup/windows11_setup.ps1:269
# Function to extract and clean up
function Expand-And-Cleanup {
param (
[string]$ZipFile,
[string]$DestinationPath,
[string]$ExpectedDirName,
[bool]$CleanupAfterExtraction
)
# FIX 1: Use absolute path based on ProjectRoot (Prevents System32 errors)
$AbsDest = Join-Path -Path $ProjectRoot -ChildPath $DestinationPath
$FinalFullPath = Join-Path -Path $AbsDest -ChildPath $ExpectedDirName
# FIX 2: Check for file/folder existence (Handle lid.176 vs lid.176.bin)
$TargetExists = $false
if (Test-Path $FinalFullPath) {
$TargetExists = $true
} elseif (Test-Path "$FinalFullPath.bin") {
# This catches the case where config says "lid.176" but file is "lid.176.bin"
$TargetExists = $true
}
if ($TargetExists) {
Write-Host " -> Target '$ExpectedDirName' already exists. Skipping extraction."
return
}
# Look for the downloaded ZIP
$FinalZipPath = Join-Path -Path $ProjectRoot -ChildPath $ZipFile
if (-not (Test-Path $FinalZipPath)) {
Write-Host "FATAL: Expected archive not found: '$FinalZipPath'" -ForegroundColor Red
Write-Host " The Python download script should have created this file." -ForegroundColor Red
exit 1
}
Write-Host " -> Extracting $ZipFile to $DestinationPath..."
# Ensure destination directory exists
if (-not (Test-Path $AbsDest)) {
New-Item -ItemType Directory -Force -Path $AbsDest | Out-Null
}
Expand-Archive -Path $FinalZipPath -DestinationPath $AbsDest -Force
if ($CleanupAfterExtraction) {
Remove-Item $FinalZipPath -Force
Write-Host " -> Cleaned up ZIP file: $ZipFile"
} else {
Write-Host " -> Keeping ZIP file: $ZipFile"
}
}
# Execute extraction for each archive
foreach ($Config in $ArchiveConfig) {
Expand-And-Cleanup -ZipFile $Config.Zip -DestinationPath $Config.Dest -ExpectedDirName $Config.Dir -CleanupAfterExtraction $should_remove_zips_after_unpack
}
Write-Host " -> Extraction and cleanup successful." -ForegroundColor Green
# --- Run initial model setup ---
Write-Host "INFO: Setting up initial models based on system language..."
# Get the 2-letter language code (e.g., "de", "fr") from Windows
$LangCode = (Get-Culture).Name.Substring(0, 2)
# Define the path to the Python script
$SetupModelScriptPath = ".\scripts\py\func\setup_initial_model.py"
# Check if the Python script exists
if (Test-Path $SetupModelScriptPath) {
Write-Host " -> Running setup_initial_model.py with language code: $LangCode"
# Execute the Python script with the detected language code
.\.venv\Scripts\python.exe $SetupModelScriptPath $LangCode
if ($LASTEXITCODE -eq 0) {
Write-Host "INFO: Initial model setup completed successfully." -ForegroundColor Green
} else {
Write-Host "WARNING: Initial model setup failed with exit code: $LASTEXITCODE" -ForegroundColor Yellow
}
} else {
Write-Host "WARNING: Initial model setup script not found at: $SetupModelScriptPath" -ForegroundColor Yellow
}
# --- Create required directories ---
$tmpPath = "C:\tmp"
$sl5DictationPath = "C:\tmp\sl5_aura"
@($tmpPath, $sl5DictationPath) | ForEach-Object {
if (!(Test-Path $_)) {
New-Item -ItemType Directory -Path $_ | Out-Null
Write-Host "Created directory: $_"
} else {
Write-Host "Directory already exists: $_"
}
}
# --- Create central config file ---
Write-Host "--> Creating central config file..."
$ConfigDir = Join-Path -Path $env:USERPROFILE -ChildPath ".config\sl5-stt"
if (-not (Test-Path -Path $ConfigDir)) {
Write-Host " -> Creating config directory at $ConfigDir"
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
}
# Korrigiert, um Backslashes für Windows zu verwenden und dann für TOML zu normalisieren
$ProjectRootPath = (Get-Location).Path
$ConfigContent = @"
[paths]
project_root = "$($ProjectRootPath.Replace('\', '/'))"
"@
$ConfigFile = Join-Path -Path $ConfigDir -ChildPath "config.toml"
Set-Content -Path $ConfigFile -Value $ConfigContent
# --- 9. Project Configuration ---
Write-Host "--> Creating Python package markers (__init__.py)..."
# Create log directory if it doesn't exist
if (-not (Test-Path "log")) {
New-Item -Name "log" -ItemType Directory | Out-Null
}
# Create __init__.py files
@("log/__init__.py", "config/__init__.py", "config/languagetool_server/__init__.py") | ForEach-Object {
$Directory = Split-Path -Path $_ -Parent
if ($Directory -and (-not (Test-Path $Directory))) {
New-Item -ItemType Directory -Path $Directory -Force | Out-Null
}
New-Item -Path $_ -ItemType File -Force | Out-Null
}
# --- 10. Completion ---
Write-Host ""
Write-Host "------------------------------------------------------------------" -ForegroundColor Green
Write-Host "------- setup/windows11_setup.ps1:413 ---" -ForegroundColor Green
Write-Host "Setup for Windows completed successfully." -ForegroundColor Green
Write-Host "------------------------------------------------------------------" -ForegroundColor Green
<#
Source - https://superuser.com/a/882297
Posted by chingNotCHing
Retrieved 2026-01-21, License - CC BY-SA 3.0
#>
Start-Process -FilePath "$PSScriptRoot\fix_permissions.bat" -Wait
#& .\SystemCheck.ps1
& "$PSScriptRoot\SystemCheck.ps1"
setup/windows11_setup_with_ahk_copyq_fzf_glogg.bat¶
REM setup/windows11_setup_with_ahk_copyq.bat
REM script_name: setup/windows11_setup_with_ahk_copyq.bat
@echo off
ECHO Starting Setup Variant: Core System + AutoHotkey + CopyQ...
REM --- ADMIN CHECK START ---
REM Force the script to run as Administrator.
REM If FSUTIL fails, we are not Admin -> Relaunch via PowerShell.
FSUTIL dirty query %systemdrive% >nul
IF %ERRORLEVEL% NEQ 0 (
ECHO Requesting Administrator privileges...
REM Restart this batch file with RunAs (Admin) and pass all original arguments
powershell -Command "Start-Process cmd -ArgumentList '/c \"\"%~dpnx0\"\" %* ' -Verb RunAs"
EXIT /B
)
ECHO Admin privileges confirmed.
REM --- ADMIN CHECK END ---
cd /d "%~dp0"
REM 1. Call the existing core setup script
REM Now running with Admin rights, so the PS1 won't try to crash-elevate itself.
@REM -Exclude 'en'"
CALL "%~dp0windows11_setup.bat" -Exclude 'en'
CALL "%~dp0windows11_setup.bat" %*
REM Check if the core setup failed
IF %ERRORLEVEL% NEQ 0 (
ECHO setup/windows11_setup_with_ahk_copyq.bat:25
ECHO Core setup encountered errors. Skipping client tools installation.
@REM PAUSE
@REM EXIT /B %ERRORLEVEL%
)
ECHO Core setup completed. Moving to AHK and CopyQ installation...
REM 2. Run the specific client tools installation script
@REM setup/windows11_setup_with_ahk_copyq.bat:34
powershell.exe -ExecutionPolicy Bypass -File "%~dp0install_ahk_copyq.ps1"
call "%~dp0glogg_installer.bat"
REM --- FZF INSTALLATION START ---
ECHO Installing fzf (Fuzzy Finder)...
winget install -e --id junegunn.fzf --source winget --accept-package-agreements --accept-source-agreements
IF %ERRORLEVEL% NEQ 0 (
ECHO fzf was already installed or an error occurred.
) ELSE (
ECHO fzf installed successfully.
)
REM --- FZF INSTALLATION END ---
ECHO.
ECHO ========================================================
ECHO Installation finished.
ECHO start "Aura Dictation" (maybe ~ 30s)...
ECHO ========================================================
@REM CALL "%~dp0..\start_dictation_v2.0.bat"
START "" explorer "%~dp0..\start_dictation_v2.0.bat"
Config-Skripte¶
config/__init__.py¶
config/settings.py¶
# config/settings.py
# Central configuration for the application
# please see also: settings_local.py_Example.txt
from scripts.py.func.determine_current_user import determine_current_user
SERVICE_START_OPTION = 0
# Option 1: Start the service only on when there is an internet connection.
current_user,_ = determine_current_user()
print(f'hi, hallo, welcome: {current_user}')
current_user = user_name = USER_NAME = str(current_user) # fallbacks
# logger.info("Current user successfully determined in a cross-platform manner.") # Add logger import if needed
# Set to True to disable certain production checks for local development,
# e.g., the wrapper script enforcement.
DEV_MODE = False
DEV_MODE_all_processing = False
soundMute = 1 # 1 is really recommended. to know when your recording is ended.
soundUnMute = 1
soundProgramLoaded = 1
ENABLE_AUTO_LANGUAGE_DETECTION = False # Deprecated . Better set it to False
# --- Notification Settings ---
# Default for new users is the most verbose level.
NOTIFICATION_LEVEL = 0 # 0=Silent, 1=Essential, 2=Verbose
SIGNATURE_COOLDOWN = 50 # 600
# 🗣️🌐 (symbols and icons are probably cut out later by )
# sometimes e.g.in twitch: gelöscht: Nightbot: @seeh74 -> Sorry, you can't post links without permission!
#🗣ടㄴ⠄de╱Aura SL5.de/Aura
signatur=' #FreedomLadder #FreeSoftware #FSF #SL5Aura SL5.de/Aura'
# signatur='🗣SL5net ⟫ Aura'
# signatur='🗣[ SL5net Aura ]'
# signatur='🗣SL5net Aura'
# signatur='🗣ടㄴ5⠄de╱Aura'
# signatur='🗣Sㄴ5⠄de╱Aura' # this l is unvisable in gemini
#signatur='🗣SL5⠄de╱Aura'
# signatur='🗣SL5⠠de╱Aura' # i like this 11.11.'25 09:58 Tue
#now (original:'jetzt', ).
#signatur=',🗣SL5。de╱Aura' # i like this 11.11.'25 09:58 Tue
signatur=''
signatur='🗣SL5net ⟫ Aura'
signatur1=f'{signatur}' # (Powered by
LANGUAGE_PREFIXES = {
"pt-br": "Tradução de Voz",
"en": "Voice Translation",
"ar": "تحدثت الترجمة",
"ja": "話し言葉の翻訳",
"de": "Sprachübersetzung",
"DEFAULT": "" # Fallback
}
# Format: { "Regex-Pattern": ( "Signatur-Text", Cooldown_in_Sekunden ) }
SIGNATURE_MAPPING = {
r"0 a\.d\.": ("SL5net >> Aura", 1), # 14400 = 4 h autocivp/moddata/autocivP_IconNames_README.txt
r"Matrix|Discord": ("🗣SL5net ⟫ Aura", 3600), # 1 Stunde für Chat
r"Outlook|Mail": ("-- Sent via Aura --", 86400), # 1 Tag für E-Mails
r"\.py|Konsole|kate|Google AI Studio|google ai studio|google ai studio": ("", 999999),
r".*": ("🗣[ SL5net Aura ]", 1800) # 30 Min Fallback
}
# --- Language Model Preloading ---
# A list of Vosk model folder names to preload at startup if memory allows.
# PRELOAD_MODELS = ["vosk-model-de-0.21", "vosk-model-en-us-0.22"] # e.g. ["vosk-model-de-0.21", "vosk-model-en-us-0.22"]
PRELOAD_MODELS = ["vosk-model-de-0.21"]
if current_user == 'SL5.de':
PRELOAD_MODELS = ["vosk-model-de-0.21"]
# config/settings.py
PLUGIN_HELPER_TTS_ENABLED = True
# USE_AS_PRIMARY_SPEAK = "piper"
USE_AS_PRIMARY_SPEAK = "ESPEAK"
USE_ESPEAK_FALLBACK = True
#testtest
ESPEAK_FALLBACK_AMPLITUDE = 50
# --- LanguageTool Server ---
# Set to True to use an existing LT server. AT YOUR OWN RISK!
# The application will not start its own server and will not stop the external one.
USE_EXTERNAL_LANGUAGETOOL = False # Default: False
# URL for the external server if the option above is True.
# EXTERNAL_LANGUAGETOOL_URL = "http://127.0.0.1:8081"
# config/settings.py
# Settings for our internal server (if used)
# --- Text Correction Settings ---
# This dictionary controls which categories of LanguageTool rules are enabled.
# The application will use these settings to enable/disable rule categories
# when checking text. Set a category to False to ignore its suggestions.
# if true call iteratively all rules
default_mode_is_all = True
# 1 zwei drei vier 5
#
LT_SKIP_RATIO_THRESHOLD = 20
"""
Explanation of the Ratio Logic:
ratio = original_text_length / made_a_change: Calculates how many characters (on average) correspond to one change.
if ratio < lt_skip_ratio_threshold: If the ratio is low (less than the safe threshold), we skip LanguageTool.
"""
show_PLUGINS_ENABLED = True
PLUGINS_ENABLED = {
"standard_actions/wikipedia_local": False,
"standard_actions/count_loud": False,
"game": False,
"empty_all": False,
}
# count_loud▉
# needs restart. implemented in the python part:
ADD_TO_SENCTENCE = "."
# set ADD_TO_SENCTENCE = "" when you dont want it.
# Recording & Transcription
SUSPICIOUS_TIME_WINDOW = 90
SUSPICIOUS_THRESHOLD = 3
# Default is SYSTEM_DEFAULT, can be set to an integer (device index)
# or a string
# Select the audio input source for the STT engine:
# ------------------------------------------------
# None:
# Standard mode. Uses the system's default input device (e.g., your headset).
#
# 'UNIFIED_AUDIO_INPUT':
# Advanced Linux-only mode. Captures both the microphone AND the desktop
# audio (extern use of Aura, system, Radio, TV … sounds) simultaneously.
# It creates a virtual "Unified_Sink" and manages routing automatically
# via 'pactl' (PulseAudio/PipeWire).
# Note: 🐧 On Linux, this will temporarily bridge your audio sources.
PRE_RECORDING_TIMEOUT = 12
# SPEECH_PAUSE_TIMEOUT = 0.6
AUDIO_INPUT_DEVICE = 'SYSTEM_DEFAULT'
# INITIAL_WAIT_TIMEOUT = initial_silence_timeout
# SPEECH_PAUSE_TIMEOUT = 2.0 # Standardwert
SPEECH_PAUSE_TIMEOUT = 1
# Standardwert
# AUDIO_INPUT_DEVICE = ' MIC_AND_DESKTOP'
# PRE_RECORDING_TIMEOUT = 12
# SPEECH_PAUSE_TIMEOUT = 5
# SPEECH_PAUSE_TIMEOUT
# Hebel, um Aura spürbar zu beschleunigen:
# Reaktionszeit (Der wichtigste Hebel): Reduziere den SPEECH_PAUSE_TIMEOUT in der settings_local.py (z. B. auf 0.5 oder 0.8). Aura wartet dann kürzer nach dem Ende deines Satzes, bevor sie loslegt. Das fühlt sich sofort "snappy" an.
SAMPLE_RATE = 16000
# System
CRITICAL_THRESHOLD_MB = 1024 * 2
LANGUAGETOOL_PORT = 8082
# LanguageTool Server Configuration
LANGUAGETOOL_BASE_URL = f"http://127.0.0.1:{LANGUAGETOOL_PORT}"
LANGUAGETOOL_CHECK_URL = f"{LANGUAGETOOL_BASE_URL}/v2/check"
LANGUAGETOOL_RELATIVE_PATH = "LanguageTool-6.6/languagetool-server.jar"
NOTIFY_SEND_PATH = "/usr/bin/notify-send"
XDOTOOL_PATH = "/usr/bin/xdotool"
TRIGGER_FILE_PATH = "/tmp/sl5_record.trigger"
# Auto-detected Java path
# JAVA_EXECUTABLE_PATH = r"/usr/bin/java"
# needs NO restart. implemented in the sh part. TODO implemt for windows:
# use . for all Windows. Other examples:
# AUTO_ENTER_AFTER_DICTATION_REGEX_APPS = "."
AUTO_ENTER_AFTER_DICTATION_REGEX_APPS = "(ExampleAplicationThatNotExist|Pi, your personal AI)"
# TODO implement for windows
# Auto-detected Java path
JAVA_EXECUTABLE_PATH = r"/usr/bin/java"
config/settings_local.py_Example.txt¶
# config/settings_local.py
import os
import pwd
# My personal settings Example
# This file is (maybe) ignored by Git.
SERVICE_START_OPTION = 0
NOTIFICATION_LEVEL = 1
soundMute = 1 # 1 is really recommandet. to know when your recording is endet.
soundUnMute = 1
soundProgramLoaded = 1
# Get username
current_user = pwd.getpwuid(os.getuid())[0]
if current_user == 'seeh':
DEV_MODE = True
# Set to True to disable certain production checks for local development,
# e.g., the wrapper script enforcement.
DEV_MODE = False
# DEV_MODE = False
DEV_MODE_memory = False
DEV_MODE_all_processing = False
PRELOAD_MODELS = ["vosk-model-de-0.21"]
PLUGIN_HELPER_TTS_ENABLED = True
signatur=''
signatur1=f''
signatur_pt_br=f''
signatur_en=f''
signatur_ar=f''
signatur_ja=f''
# --- Custom Correction Settings ---
# Import the default dictionary from the main settings file.
try:
from .settings import CORRECTIONS_ENABLED
except ImportError:
CORRECTIONS_ENABLED = {} # Fallback in case the import fails
# Update the dictionary with my personal preferences.
# I want specialized legal and medical checks, but I don't want style advice.
CORRECTIONS_ENABLED.update({
"git": True,
})
CORRECTIONS_ENABLED.update({
})
PRE_RECORDING_TIMEOUT = 12
SPEECH_PAUSE_TIMEOUT = 1
# for more poweruser mor in hurry:
AUDIO_INPUT_DEVICE = 'SYSTEM_DEFAULT'
# needs NO restart:
PRE_RECORDING_TIMEOUT = 6
SPEECH_PAUSE_TIMEOUT = 2
#test (original:'test', 🗣SL5。de╱Aura).
if current_user == 'sl5':
# needs NO restart:
PRE_RECORDING_TIMEOUT = 4
SPEECH_PAUSE_TIMEOUT = 1
# examples:
# AUTO_ENTER_AFTER_DICTATION_REGEX_APPS = "."
# needs NO restart. implemented in the sh part. TODO implemt for windows:
AUTO_ENTER_AFTER_DICTATION_REGEX_APPS = "(ExampleAplicationThatNotExist|Pi, your personal AI)"
ADD_TO_SENCTENCE = "."
# set ADD_TO_SENCTENCE = "" when you dont want it.
CRITICAL_THRESHOLD_MB = 1024 * 2
# CRITICAL_THRESHOLD_MB = 28000 # (also 28 GB)
USE_ESPEAK_FALLBACK = True
ESPEAK_FALLBACK_AMPLITUDE = 80
show_PLUGINS_ENABLED = True
# --- Custom Client-Side Plugins ---
# Enable or disable specific client-side behaviors (plugins).
# The logic is handled by client scripts (e.g., type_watcher.sh, AutoKey).
# These settings tell the backend service what to expect or how to format output.
#### "github_corrections": True, # Example from before ???
PLUGINS_ENABLED = {
"empty_all": False,
"git": True,
"wannweil": True,
"game/dealers_choice": False,
"game/0ad": False,
"game/": False,
"standard_actions/wikipedia_local/": False,
"ethiktagung": False,
"it-begriffe.php.codeigniter": False
"volkshochschule_tue": True,
"CCC_tue": True,
"vsp_rt": True,
"ki-maker-space": True,
"numbers_to_digits": True, # hundert|einhundert --> 100
"digits_to_numbers": False,
"web-radio-funk": True,
"it-begriffe": False,
"it-begriffe/php/codeigniter": True,
}
Githooks-Skripte¶
githooks/pre-commit¶
#!/bin/bash
# .git/hooks/pre-commit
# --- Start of flake8 check ---
echo "Running flake8 code quality check..."
# Activate virtual environment if it exists
if [ -f .venv/bin/activate ]; then
source .venv/bin/activate
fi
# Run flake8 on specific directories
flake8 ./aura_engine.py ./scripts ./config
# flake8 ./scripts ./config/languagetool_server/maps
# Abort commit if flake8 finds errors
if [ $? -ne 0 ]; then
echo "COMMIT REJECTED: flake8 found issues."
exit 1
fi
echo "flake8 check passed."
# --- End of flake8 check ---
# Check if pipreqs is available in the venv
if [ ! -x ".venv/bin/pipreqs" ]; then
echo "pipreqs is not installed in .venv. Please run '.venv/bin/pip install pipreqs' inside your virtual environment."
exit 1
fi
TMPDIR=".pipreqs_temp"
# Clean up and create a fresh temp directory
rm -rf "$TMPDIR"
mkdir "$TMPDIR"
# Copy all relevant Python sources into the temp directory
cp aura_engine.py "$TMPDIR/"
cp get_suggestions.py "$TMPDIR/"
# Das gesamte 'scripts'-Verzeichnis kopieren, damit Import-Pfade erhalten bleiben.
cp -r scripts "$TMPDIR/"
echo "INFO: Generating requirements.txt (take into account .pipreqs-whitelist.txt) ..."
# --- FINALE KORREKTUR HIER: ---
# Wir benutzen --savepath, um pipreqs anzuweisen, die Datei im aktuellen
# Verzeichnis zu speichern, während es das Temp-Verzeichnis analysiert.
# Die Warnungen fangen wir weiterhin für die Whitelist ab.
WARNINGS=$(.venv/bin/pipreqs "$TMPDIR" --savepath ./requirements.txt --force --encoding=utf-8 2>&1 >/dev/null)
# Filtere die Warnungen. Zeige nur die an, die NICHT in der Whitelist stehen.
if [ -s ".pipreqs-whitelist.txt" ]; then
# Zeige nur die Zeilen, die nicht in der Whitelist sind
echo "$WARNINGS" | grep -v -f .pipreqs-whitelist.txt | grep -v "Please, verify manually"
else
# Wenn die Whitelist leer ist, zeige alle Warnungen
echo "$WARNINGS"
fi
# Remove known false positives like our own 'scripts' directory
sed -i '/^scripts==/d' requirements.txt
# Convert package names to lowercase
awk -F'==' '/==/ {print tolower($1) "==" $2}' requirements.txt > requirements.txt.tmp && mv requirements.txt.tmp requirements.txt
# --- PLATFORM-SPECIFIC FASTTEXT FIX ---
# pipreqs generates 'fasttext==X.X.X'. We need to replace it with the
# platform-specific versions for Windows and Linux/MacOS.
echo "INFO: Applying Fasttext platform-specific fix..."
# 1. Remove the line generated by pipreqs
sed -i '/^fasttext==/d' requirements.txt
# 2. Append the platform-specific rules explicitly
echo "fasttext==0.9.3; sys_platform != 'win32'" >> requirements.txt
echo "fasttext-wheel==0.9.2; sys_platform == 'win32'" >> requirements.txt
# ------------------------------------
# githooks/pre-commit:88
# B) WINDOWS AUDIO & CONTROL (Persistent on Linux)
# Remove potential entries to prevent duplicates or missing markers
sed -i '/^comtypes/d' requirements.txt
sed -i '/^pycaw/d' requirements.txt
# Append explicit Windows rules
echo "comtypes==1.4.11; sys_platform == 'win32'" >> requirements.txt
echo "pycaw==20240210; sys_platform == 'win32'" >> requirements.txt
# Clean up temp directory
rm -rf "$TMPDIR"
# add some explizit that e.g. used in little config/maps/plugin scripts
echo "wikipedia-api" >> requirements.txt
echo "bs4" >> requirements.txt
echo "nltk==3.9.1" >> requirements.txt
cp .git/hooks/pre-commit docs/pre-commit_backup.txt
Update-Skripte¶
update/update_for_linux_users.sh¶
#!/bin/bash
# file: update/update_for_linux_users.sh
# Description: Downloads the latest version and updates the application
# while preserving user settings. For non-developer use.
# Stoppt das Script sofort bei einem Fehler
set -e
# Farbdefinitionen für die Konsole
COLOR_CYAN='\e[36m'
COLOR_GREEN='\e[32m'
COLOR_RED='\e[31m'
COLOR_YELLOW='\e[33m'
COLOR_RESET='\e[0m'
# Variablen
repoUrl="https://github.com/sl5net/SL5-aura-service/archive/refs/heads/master.zip"
# Den Installationspfad ermitteln (zwei Ebenen über dem Script-Pfad)
# Annahme: Script liegt in 'installDir/update/'
scriptDir="$(dirname "$0")"
installDir="$(dirname "$scriptDir")"
tempDir="/tmp/sl5_update_temp"
localSettingsPath="$installDir/config/settings_local.py"
backupPath="$tempDir/settings_local.py.bak"
zipPath="$tempDir/latest.zip"
# Funktion zur Fehlerbehandlung
cleanup_on_error() {
echo -e "${COLOR_RED}FATAL: Ein Fehler ist während des Updates aufgetreten.${COLOR_RESET}" >&2
if [ -d "$tempDir" ]; then
echo -e "${COLOR_YELLOW}INFO: Lösche temporären Ordner...${COLOR_RESET}"
rm -rf "$tempDir"
fi
read -p "Drücken Sie die Eingabetaste, um das Script zu beenden."
exit 1
}
# Fange Fehler ab (trap)
trap cleanup_on_error ERR
echo -e "${COLOR_CYAN}--- SL5 Aura Updater ---${COLOR_RESET}"
echo "Dies lädt die neueste Version herunter und ersetzt alle Anwendungsdateien."
echo "Ihre persönlichen Einstellungen in 'config/settings_local.py' werden gespeichert."
if [ "$CI" != "true" ]; then
echo "Bitte schließen Sie die Hauptanwendung, falls sie läuft."
read -p "Drücken Sie die Eingabetaste, um fortzufahren, oder CTRL+C, um abzubrechen"
fi
# 1. Alte temporäre Dateien bereinigen und neuen Ordner erstellen
if [ -d "$tempDir" ]; then
echo "INFO: Entferne alten temporären Update-Ordner..."
rm -rf "$tempDir"
fi
mkdir -p "$tempDir"
# 2. Lokale Einstellungen sichern, falls vorhanden
if [ -f "$localSettingsPath" ]; then
echo -e "${COLOR_GREEN}INFO: Sichern Ihrer lokalen Einstellungen...${COLOR_RESET}"
cp "$localSettingsPath" "$backupPath"
fi
# 3. Neueste Version herunterladen (curl oder wget erforderlich)
echo "INFO: Lade neueste Version von GitHub herunter..."
curl -L -o "$zipPath" "$repoUrl"
# 4. Archiv entpacken (unzip erforderlich)
echo "INFO: Entpacke Update..."
unzip -q "$zipPath" -d "$tempDir"
# Den extrahierten Ordner finden (der normalerweise auf '-master' endet)
extractedFolder=$(find "$tempDir" -maxdepth 1 -type d -name "*-master" | head -n 1)
if [ -z "$extractedFolder" ]; then
echo -e "${COLOR_RED}FATAL: Konnte den extrahierten '*-master' Ordner nicht finden.${COLOR_RESET}"
exit 1
fi
# 5. Lokale Einstellungen in die neue Version wiederherstellen
if [ -f "$backupPath" ]; then
echo -e "${COLOR_GREEN}INFO: Stelle Ihre lokalen Einstellungen in die neue Version wieder her...${COLOR_RESET}"
cp "$backupPath" "$extractedFolder/config/"
fi
# 6. Dateien ersetzen (rsync erforderlich)
# Mit rsync die Dateien aus dem temporären Ordner in den Installationsordner kopieren und die alten Dateien löschen.
echo "INFO: Finalisiere Update und ersetze Dateien. Bitte warten..."
# Die '-a' Option bewahrt Berechtigungen, Zeiten etc.
# Die '--delete' Option entfernt Dateien, die im Quellverzeichnis nicht vorhanden sind.
# Der abschließende Schrägstrich bei "$extractedFolder/" ist wichtig, um den *Inhalt* zu kopieren.
# rsync -a --delete --force "$extractedFolder/" "$installDir"
# --exclude='__pycache__' \
# --exclude='*.pyc' \
rsync -a --delete --force \
--max-size=100M \
--exclude='.venv' \
--exclude='models' \
--exclude='LanguageTool*' \
--exclude='.git' \
"$extractedFolder/" "$installDir"
# 7. Aufräumen
echo "INFO: Bereinige temporäre Dateien..."
rm -rf "$tempDir"
source .venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements.txt
echo ""
echo -e "${COLOR_GREEN}Update abgeschlossen! Sie können die Anwendung jetzt neu starten.${COLOR_RESET}"
# Keine Fehlerbehandlung notwendig, wenn das Script bis hierher kommt
trap - ERR
read -p "Drücken Sie die Eingabetaste, um das Script zu beenden."
exit 0
update/update_for_windows_users.ps1¶
# file: update/update_for_windows_users.ps1
# Description: Downloads the latest version and updates the application
# while preserving user settings. For non-developer use.
$ErrorActionPreference = 'Stop'
$repoUrl = "https://github.com/sl5net/SL5-aura-service/archive/refs/heads/master.zip"
$installDir = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
$tempDir = Join-Path $env:TEMP "sl5_update_temp"
Write-Host "--- SL5 Aura Updater ---" -ForegroundColor Cyan
Write-Host "This will download the latest version and replace all application files."
Write-Host "This will download the latest version and replace all application files."
Write-Host "This will download the latest version and replace all application files."
Write-Host "Your personal settings in 'config\settings_local.py' will be saved."
Write-Host "Your personal settings in 'config\settings_local.py' will be saved."
Write-Host "Your personal settings in 'config\settings_local.py' will be saved."
Write-Host "Your personal settings in 'config\settings_local.py' will be saved."
Write-Host "Your personal settings in 'config\settings_local.py' will be saved."
Write-Host "Your personal settings in 'config\settings_local.py' will be saved."
if (-not ($env:CI -eq 'true'))
{
Write-Host "this will take some time ..."
# Write-Host "Please close the main application if it is running."
# Read-Host -Prompt "Press Enter to continue or CTRL+C to cancel"
}
try {
# 1. Clean up previous temporary files if they exist
if (Test-Path $tempDir) {
Write-Host "INFO: Removing old temporary update folder..."
Remove-Item -Path $tempDir -Recurse -Force
}
New-Item -Path $tempDir -ItemType Directory | Out-Null
# 2. Backup local settings if they exist
$localSettingsPath = Join-Path $installDir "config\settings_local.py"
$backupPath = Join-Path $tempDir "settings_local.py.bak"
if (Test-Path $localSettingsPath) {
Write-Host "INFO: Backing up your local settings..." -ForegroundColor Green
Copy-Item -Path $localSettingsPath -Destination $backupPath
}
# 3. Download the latest version
$zipPath = Join-Path $tempDir "latest.zip"
Write-Host "INFO: Downloading latest version from GitHub..."
Invoke-WebRequest -Uri $repoUrl -OutFile $zipPath
# 4. Extract the archive
Write-Host "INFO: Extracting update..."
Expand-Archive -Path $zipPath -DestinationPath $tempDir -Force
$extractedFolder = Get-ChildItem -Path $tempDir -Directory | Where-Object { $_.Name -like '*-master' } | Select-Object -First 1
if (-not $extractedFolder) { throw "Could not find extracted '*-master' folder." }
# 5. Restore local settings into the new version
if (Test-Path $backupPath) {
Write-Host "INFO: Restoring your local settings into the new version..." -ForegroundColor Green
Copy-Item -Path $backupPath -Destination (Join-Path $extractedFolder.FullName "config\")
}
# 6. Create a final batch script to perform the file replacement and update dependencies
# $installerName = "setup\windows11_setup.bat"
$installerName = "setup\windows11_setup_with_ahk_copyq.bat"
$batchScript = @'
@echo off
echo Finalizing update, please wait...
timeout /t 3 /nobreak > nul
:: 1. Dateien verschieben
robocopy "{0}" "{1}" /E /MOVE /NFL /NDL /NJH /NJS > nul
:: 2. In Zielordner wechseln
cd /d "{1}"
:: 3. Abhängigkeiten aktualisieren
echo.
echo ---------------------------------------------------
echo Updating dependencies (pip install)...
echo ---------------------------------------------------
:: 3. Installer aufrufen (Dort passiert das pip upgrade und requirements install)
if exist "{2}" (
echo Starting installer script...
call "{2}"
) else (
color 4f
echo.
echo ===================================================
echo FATAL ERROR: SETUP SCRIPT NOT FOUND!
echo Expected: {2} in %CD%
echo ===================================================
echo WARNING: "{2}" not found. Trying manual pip upgrade...
:: Fallback, falls keine install.bat da ist:
if exist ".venv\Scripts\activate.bat" (
call .venv\Scripts\activate.bat
python.exe -m pip install --upgrade pip
pip install -r requirements.txt
color 07
) else (
echo The update cannot proceed. Please verify the filename.
pause
exit /b 1
)
)
echo.
echo Update complete! You can now restart the application.
timeout /t 10 > nul
del "%~f0"
'@ -f $extractedFolder.FullName, $installDir, $installerName
$batchPath = Join-Path $installDir "_finalize_update.bat"
Set-Content -Path $batchPath -Value $batchScript
# update/update_for_windows_users.ps1:110
# 7. Launch the batch script and exit this PowerShell script
Write-Host "INFO: Handing over to final updater script. This window will close." -ForegroundColor Yellow
Start-Process cmd.exe -ArgumentList "/C `"$batchPath`""
} catch {
Write-Host "FATAL: An error occurred during the update." -ForegroundColor Red
Write-Host $_.Exception.Message -ForegroundColor Red
Read-Host -Prompt "Press Enter to exit."
}
Scripts-Skripte¶
scripts/__init__.py¶
scripts/activate-venv_and_run-server.sh¶
#!/bin/bash
# scripts/activate-venv_and_run-server.sh
# Exit immediately if a command fails
SCRIPT_firstName="aura_engine"
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
PROJECT_ROOT="$SCRIPT_DIR/.."
os_type=$(uname -s)
if [[ "$os_type" == "MINGW"* || "$os_type" == "CYGWIN"* || "$os_type" == "MSYS"* ]]; then
# This is a Windows-based shell environment
detected_os="windows"
else
# This is any other OS (Linux, macOS, FreeBSD, etc.)
detected_os="other"
fi
if [ "$detected_os" = "windows" ]; then
echo "please start type_watcher.ahk"
echo "please start trigger-hotkeys.ahk"
else
$PROJECT_ROOT/type_watcher_keep_alive.sh &
fi
set -e
HEARTBEAT_FILE="/tmp/$SCRIPT_firstName.heartbeat"
SCRIPT_TO_START="$SCRIPT_DIR/../$SCRIPT_firstName.py"
MAX_STALE_SECONDS=5
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus"
export DISPLAY=:0
export XAUTHORITY=${HOME}/.Xauthority
export DICTATION_SERVICE_STARTED_CORRECTLY="true"
if [ -f "$HEARTBEAT_FILE" ]
then
last_update=$(cat "$HEARTBEAT_FILE")
current_time=$(date +%s)
age=$((current_time - last_update))
if [ "$age" -lt "$MAX_STALE_SECONDS" ]
then
echo "Service appears to be running and healthy."
exit 0
else
echo "Service heartbeat is stale. Attempting to restart."
fi
else
echo "Service is not running."
fi
python3 -m venv .env
echo "Activating virtual environment at '$PROJECT_ROOT/venv'..."
python3 -m venv .venv
source "$PROJECT_ROOT/.venv/bin/activate"
echo "Starting Python server from '$PROJECT_ROOT'..."
# We run the python script using its absolute path to be safe
echo "Starting service..."
# export PYTHONDONTWRITEBYTECODE=1
PYTHONDONTWRITEBYTECODE=1 python3 "$SCRIPT_TO_START" &
scripts/notification_watcher.ahk¶
#Requires AutoHotkey v2.0
#SingleInstance Force ; is buggy, using Heartbeat mechanism instead
/**
* @file notification_watcher.ahk
* @description Displays non-blocking system notifications and feedback
* from the STT background service.
* https://www.autohotkey.com/docs/v2/Language.htm#comments
*/
A_IconTip := "SL5 Aura Notifier"
; Create a borderless, always-on-top GUI window for our notification
noteGui := Gui("+AlwaysOnTop -Caption +ToolWindow")
noteGui.SetFont("s10", "Segoe UI")
titleCtrl := noteGui.Add("Text", "w300")
noteGui.SetFont("s9", "Segoe UI")
bodyCtrl := noteGui.Add("Text", "w300 y+5")
SetTimer(WatchForNotification, 250)
WatchForNotification() {
static notifyFile := "C:\tmp\notification.txt"
if FileExist(notifyFile) {
content := Trim(FileRead(notifyFile))
FileDelete(notifyFile)
if (content = "")
return
parts := StrSplit(content, "|")
titleCtrl.Text := parts[1]
bodyCtrl.Text := parts.Has(2) ? parts[2] : ""
; Position and show the GUI
noteGui.Show("NA")
; Hide it after 5 seconds
SetTimer(HideGui, -5000)
}
}
HideGui() {
noteGui.Hide()
}
scripts/restart_venv_and_run-server.ahk¶
#Requires AutoHotkey v2.0
; restart_venv_and_run-server.ahk
FileReadLine(File, LineNumber)
{
FileRead FileContents, %File%
FileContents := ""
Loop Parse, FileContents, "`n", "`r"
{
If (A_Index = LineNumber)
return Trim(A_LoopField)
}
return ""
}
; Setzen Sie den Pfad zum Skript-Verzeichnis
; ScriptDir := FileGetDir(A_ScriptFullPath)
ScriptDir := A_ScriptDir
; Setzen Sie den Pfad zur Konfigurationsdatei
ConfigFile := ScriptDir . "\..\config\server.conf"
; Setzen Sie den Pfad zum Server-Skript
ServerScript := ScriptDir . "\activate-venv_and_run-server.sh"
; Lese die Konfigurationsdatei und lade die Variable PORT
FileRead ConfigFile, "r"
PORT := Trim(FileReadLine(ConfigFile, 1), " `t")
; Starte den Server neu
MsgBox "Restarting TTS Server on Port " PORT "..."
Run "pkill -f aura_engine.py"
Run "pkill -f type_watcher.sh"
; DO NOT kill LanguageTool server here. Run "pkill -f languagetool-server.jar"
Sleep 1000
; Überprüfe, ob ein Prozess auf dem Port läuft
PID := ""
PID := Run("lsof -t -i :" PORT, "", "", Output)
if (PID != "")
{
Run "kill " PID
Sleep 1000
}
; Starte das Server-Skript neu
Run ServerScript
scripts/restart_venv_and_run-server.sh¶
#!/bin/bash
#
# restart_venv_and_run-server.sh
#
# Final version: Correctly terminates ALL associated processes (main service and watcher)
# and reliably waits for them to disappear before starting a new instance.
# --- Configuration ---
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
SERVER_SCRIPT="$SCRIPT_DIR/activate-venv_and_run-server.sh"
SERVICE_NAME_MAIN="aura_engine.py"
os_type=$(uname -s)
if [[ "$os_type" == "MINGW"* || "$os_type" == "CYGWIN"* || "$os_type" == "MSYS"* ]]; then
# This is a Windows-based shell environment
detected_os="windows"
else
# This is any other OS (Linux, macOS, FreeBSD, etc.)
detected_os="other"
fi
if [ "$detected_os" = "windows" ]; then
echo "please start type_watcher.ahk"
echo "please start trigger-hotkeys.ahk"
else
SERVICE_NAME_WATCHER="type_watcher_keep_alive.sh"
SERVICE_NAME_WATCHER2="type_watcher_keep_alive.sh"
fi
echo "Requesting restart for all services..."
# --- Step 1: Check if any of the target processes are running ---
# The -f flag for pgrep searches the full command line.
if ! pgrep -f "$SERVICE_NAME_MAIN" > /dev/null && ! pgrep -f "$SERVICE_NAME_WATCHER" > /dev/null; then
echo "Info: No running server or watcher processes found. Starting fresh."
else
# --- Step 2: Kill ALL old processes (Main Service AND Watcher) ---
echo "Stopping old processes..."
# Use -f to match the full command line, just like in pgrep.
# pkill -f "$SERVICE_NAME_MAIN"
pkill -9 "$SERVICE_NAME_MAIN"
pkill -f "$SERVICE_NAME_WATCHER"
echo pkill -9 -f "$SERVICE_NAME_MAIN"
echo pkill -9 -f "$SERVICE_NAME_WATCHER"
echo pkill -9 -f "$SERVICE_NAME_WATCHER2"
# realpath /tmp/../tmp/../tmp
# /tmp
# PROJECT_ROOT=realpath $SCRIPT_DIR/..
PROJECT_ROOT=$(realpath "$SCRIPT_DIR/..")
echo SCRIPT_DIR=$SCRIPT_DIR
echo PROJECT_ROOT=$PROJECT_ROOT
echo "Activating virtual environment at '$PROJECT_ROOT/venv'..."
cd $PROJECT_ROOT
# Sometimes it can help for performance (original:'manchmal kann es helfen aus performance Gründen').
# to loose all cash files (original:'wenn alle cash files entgernt werden').
find . | grep -E "(__pycache__|\.pyc|\.pyo$)" | xargs rm -rf
python3 -m venv .venv
source .venv/bin/activate
end_aura_enginePY="$PROJECT_ROOT/scripts/py/end_aura_engine.py"
echo end_aura_enginePY=$end_aura_enginePY
python3 "$end_aura_enginePY" &
# --- Step 3: Reliably wait for BOTH processes to terminate ---
echo -n "Waiting for all processes to shut down "
TIMEOUT_SECONDS=10
for (( i=0; i<TIMEOUT_SECONDS; i++ )); do
# The loop continues as long as EITHER the main service OR the watcher is found.
if ! pgrep -f "$SERVICE_NAME_MAIN" > /dev/null && ! pgrep -f "$SERVICE_NAME_WATCHER" > /dev/null; then
echo -e "\nInfo: All services have been terminated."
break # Exit the loop, we are done waiting.
fi
echo -n "."
sleep 1
done
fi
sleep 1
# --- Step 4: Start the new server instance ---
echo "Starting new server and watcher..."
if [ -x "$SERVER_SCRIPT" ]; then
"$SERVER_SCRIPT"
else
echo "Error: Server script is not executable: $SERVER_SCRIPT"
echo "Please run: chmod +x $SERVER_SCRIPT"
exit 1
fi
echo "Server started or closed now."
scripts/type_watcher_Stash_Wird_bald_geloescht.sh¶
#!/bin/bash
# type_watcher_keep_alive.sh (Version 4 - Final)
# --- Set FULL Environment explicitly for background tools ---
export DISPLAY=:0
export XAUTHORITY=${HOME}/.Xauthority
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus"
# ... (der Rest des Skripts bleibt exakt gleich) ...
# --- Dependency Check ---
if ! command -v inotifywait &> /dev/null || ! command -v xdotool &> /dev/null; then
exit 1
fi
DIR_TO_WATCH="/tmp"
# DIR_TO_WATCH="${HOME}/.sl5_stt_tmp"
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
PROJECT_ROOT="$SCRIPT_DIR/.."
FILE_PATTERN_BASE="tts_output_"
# LOCKFILE="/tmp/type_watcher.lock"
LOCKFILE="${DIR_TO_WATCH}/type_watcher.lock" # Lockfile also moves
LOG_FILE="$PROJECT_ROOT/type_watcher.log"
if [ -e "$LOCKFILE" ] && ps -p "$(cat "$LOCKFILE")" > /dev/null; then
exit 0
fi
echo $$ > "$LOCKFILE"
trap "rm -f $LOCKFILE" EXIT
inotifywait -m -q -e create --format '%f' "$DIR_TO_WATCH" | while read -r FILE; do
if [[ "$FILE" == ${FILE_PATTERN_BASE}* ]]; then
FULL_PATH="${DIR_TO_WATCH}/${FILE}"
sleep 0.05
if [ -s "$FULL_PATH" ]; then
TEXT=$(cat "$FULL_PATH")
rm "$FULL_PATH"
[ -n "$TEXT" ] && xdotool type --clearmodifiers "$TEXT"
fi
fi
done
Root-Verzeichnis Skripte¶
Folgende Skripte sind hier dokumentiert:
start_dictation_v2.0.batupdate.batinstall_hooks.shtype_watcher.ahktype_watcher_keep_alive.sh
start_dictation_v2.0.bat¶
:: start this script like: & .\start_dictation_v2.0.bat
:: Version: v2.1
@echo off
setlocal
title SL5 Aura - One-Click Starter
:: --- Step 1: Set correct working directory ---
cd /d "%~dp0"
:: --- 2. Admin Rights Check ---
echo [*] Checking for Administrator privileges
REM Only check for admin rights if NOT running in a CI environment
if /I NOT "%CI%"=="true" (
net session >nul 2>&1
if %errorLevel% neq 0 (
echo [ERROR] Re-launching with Admin rights...
REM update test: 2026-0122-1421
REM powershell -Command "Start-Process '%~f0' -Verb RunAs"
REM powershell -Command "Start-Process cmd -ArgumentList '/c, %~f0' -Verb RunAs"
REM Start-Process -FilePath "powershell.exe" -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File', $PSCommandPath) -Verb RunAs
REM Start-Process -FilePath "powershell.exe" -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File', $PSCommandPath -Verb RunAs
REM powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0\start_dictation_v2.0.ps1"
powershell -Command "Start-Process cmd -ArgumentList '/c, """%~f0"""' -Verb RunAs"
exit /b
)
)
echo [SUCCESS] Running with Administrator privileges.
:: --- Step 3: VEREINFACHT - Check if venv exists, otherwise run full setup ---
if not exist ".\.venv\Scripts\python.exe" (
echo [WARNING] Virtual environment is missing or incomplete.
echo [ACTION] Running full setup. This may take a moment...
pause
REM .\setup\windows11_setup.ps1 -Exclude "en" or .\setup\windows11_setup.ps1 -Exclude "de" or .\setup\windows11_setup.ps1 -Exclude "all".
powershell.exe -ExecutionPolicy Bypass -File ".\setup\windows11_setup.ps1" -Exclude "en"
if not exist ".\.venv\Scripts\python.exe" (
echo [FATAL] Automated setup failed. Please check setup script.
pause
exit /b
)
echo [SUCCESS] Virtual environment has been set up successfully.
)
echo.
powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and ($_.CommandLine -like '*type_watcher.ahk*') } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"
powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and ($_.CommandLine -like '*notification_watcher.ahk*') } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"
powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and ($_.CommandLine -like '*trigger-hotkeys.ahk*') } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"
:: --- Step 4: Start background components ---
start "SL5 Type Watcher.ahk" type_watcher.ahk
start "SL5 Notification Watcher.ahk" scripts\notification_watcher.ahk
schtasks /query /tn "AuraDictation_Hotkeys" /v | findstr /I "Running" >nul
if %errorlevel% equ 0 (
echo [INFO] Admin-Hotkeys are already running.
) else (
echo [INFO] Starting Hotkeys in User-Mode...
:: HIER muss der Start-Befehl stehen, nicht ein Kill-Befehl!
:: --- NEU: Warte 2 Sekunden, damit Windows die Datei-Locks freigeben kann ---
timeout /t 2 /nobreak >nul
start "Trigger Hotkeys" trigger-hotkeys.ahk
)
echo [INFO] Background watchers have been started.
echo.
:: --- Step 5: Activate venv and start the main service with auto-repair ---
echo [INFO] Activating virtual environment...
call .\.venv\Scripts\activate.bat
set REPAIR_ATTEMPTED=
:START_SERVICE_LOOP
echo [INFO] Starting the Python STT backend service...
:: python -u aura_engine.py
python -X utf8 -u aura_engine.py
echo [INFO] Waiting 5 seconds for the service to initialize...
timeout /t 5 >nul
echo [INFO] Verifying service status via log file...
findstr /C:"Setup validation successful" "log\aura_engine.log" >nul 2>&1
IF %ERRORLEVEL% EQU 0 goto :CONTINUE_SCRIPT
:: --- ERROR HANDLING & REPAIR ---
echo [WARNING] Service verification failed. Log does not contain success signal.
if defined REPAIR_ATTEMPTED (
echo [FATAL] The automatic repair attempt has failed. Please check setup manually.
pause
exit /b 1
)
echo [ACTION] Attempting automatic repair by reinstalling dependencies...
set REPAIR_ATTEMPTED=true
call .\.venv\Scripts\python.exe -m pip install -r requirements.txt
echo [INFO] Repair finished. Retrying service start...
echo.
goto :START_SERVICE_LOOP
:CONTINUE_SCRIPT
echo [INFO] Service verification successful.
echo [*] Triggering the service...
echo. >> "c:/tmp/sl5_record.trigger"
echo.
:: --- Final Success Message - ENTFERNT die doppelte Meldung ---
echo [SUCCESS] SL5 Aura is now running in the background.
echo This window will close automatically in a few seconds.
timeout /t 5 > nul
update.bat¶
@echo off
:: file: update.bat
:: Description: One-click updater for Windows users.
:: This script requests admin rights and then runs the main PowerShell update script.
pushd "%~dp0"
if "%CI%"=="true" goto run_script
:: ---------------------------------------------------------
:: 1. SCHREIB-TEST (Write Permission Check)
:: ---------------------------------------------------------
:: Wir versuchen, eine unsichtbare Test-Datei zu erstellen.
echo test > ".write_permission_check.tmp" 2>nul
:: update.bat:17
if exist ".write_permission_check.tmp" (
del ".write_permission_check.tmp"
echo Schreibrechte vorhanden. Starten ohne Admin-Rechte...
goto run_script
)
:: 1. Check for administrative privileges
net session >nul 2>&1
if %errorLevel% NEQ 0 (
echo Requesting administrative privileges to run the updater...
powershell -Command "Start-Process -FilePath '%0' -Verb RunAs"
Start-Sleep -Seconds 2.5
)
call "%~dp0setup\fix_permissions.bat"
:: 2. Now that we have admin rights, run the actual PowerShell updater script
:: -ExecutionPolicy Bypass: Temporarily allows the script to run without changing system settings.
:: -File: Specifies the script to execute.
:run_script
echo Starting the update process...
powershell.exe -ExecutionPolicy Bypass -File "%~dp0update\update_for_windows_users.ps1"
echo.
echo The update script has finished. This window can be closed.
powershell -c "[System.Media.SystemSounds]::Asterisk.Play()"
call "%~dp0..\start_dictation_v2.0.bat"
install_hooks.sh¶
#!/bin/bash
cp githooks/pre-push .git/hooks/pre-push
chmod +x .git/hooks/pre-push
echo "pre-push hook installed!"
type_watcher.ahk¶
#Requires AutoHotkey v2.0
#SingleInstance Off
; #SingleInstance Force ; is buggy, using Heartbeat mechanism instead
/**
*
„Could not close the previous instance“ tritt genau dann auf, wenn ein Skript in einem Alertable State (SleepEx) oder einem tiefen DllCall (wie Folder-Watcher) feststeckt. AHKs Standardbefehl kann den Prozess dann nicht sauber beenden.
*/
/**
* @file type_watcher.ahk
* @description Monitors the STT output folder and types incoming text.
* Includes a "Zombie Map" state machine to prevent double-processing
* due to redundant Windows file system events.
* https://www.autohotkey.com/docs/v2/Language.htm#comments
*
*
* planed:
* ListLines(False)
*
*/
; --- Configuration ---
watchDir := "C:\tmp\sl5_aura"
logDir := A_ScriptDir "\log"
autoEnterFlagPath := "C:\tmp\sl5_auto_enter.flag"
heartbeat_start_File := "C:\tmp\heartbeat_type_watcher_start.txt"
global processedZombies := Map() ; Key: FullPath, Value: Timestamp
global isProcessingQueue := false
global fileStates := Map() ; Mögliche Zustände: "queued", "processing", "done"
; Am Anfang des Skripts (bevor der Watcher startet)
CleanTempFolder() {
global watchDir
Log("Cleaning up old session files...")
Loop Files, watchDir "\tts_output_*.txt" {
try {
FileDelete(A_LoopFileFullPath)
}
}
}
CleanTempFolder() ; Einmal beim Start ausführen
; --- Main Script Body ---
myUniqueID := A_TickCount . "-" . Random(1000, 9999)
try {
fileHandle := FileOpen(heartbeat_start_File, "w")
fileHandle.Write(myUniqueID)
fileHandle.Close()
} catch as e {
MsgBox("FATAL: Could not write heartbeat file: " . e.Message, "Error", 16)
ExitApp
}
; --- SendMode Event is safer than InputThenPlay on new Windows builds ---
SendMode('Event')
SetKeyDelay(10, 10)
; type_watcher.ahk:29
; --- Global Variables ---
global pBuffer := Buffer(1024 * 16), hDir, pOverlapped := Buffer(A_PtrSize * 2 + 8, 0)
global CompletionRoutineProc
global watcherNeedsRearm := false
global fileQueue := []
global isProcessingQueue := false
Sleep(200)
try {
if FileExist(heartbeat_start_File) {
lastUniqueID := Trim(FileRead(heartbeat_start_File))
if (lastUniqueID != myUniqueID) {
ExitApp ; other instance exists
}
}
} catch {
ExitApp
}
SetTimer(CheckHeartbeatStart, 5000)
CheckHeartbeatStart() {
global heartbeat_start_File, myUniqueID
try {
local lastUniqueID := Trim(FileRead(heartbeat_start_File, "UTF-8"))
if (lastUniqueID != myUniqueID) {
Log("Newer instance detected. Terminating self.")
ExitApp
}
} catch {
ExitApp
}
}
DirCreate(watchDir)
DirCreate(logDir)
Log("--- Script Started (v9.0 - Fixed Callback Freeze) ---")
Log("Watching folder: " . watchDir)
; type_watcher.ahk:69
CompletionRoutineProc := CallbackCreate(IOCompletionRoutine, "F", 3)
WatchFolder(watchDir)
ProcessExistingFiles()
; --- The Main Application Loop ---
Loop {
; This puts the script in "Alertable State" so callbacks can fire.
DllCall("SleepEx", "UInt", 0xFFFFFFFF, "Int", true)
if (watcherNeedsRearm) {
Log("MainLoop: Detected re-arm flag. Calling ReArmWatcher.")
watcherNeedsRearm := false
ReArmWatcher()
}
}
Log("--- FATAL: Main loop exited unexpectedly. ---")
ExitApp
; =============================================================================
; LOGGING
; =============================================================================
Log(message) {
static logFile := logDir "\type_watcher.log"
try {
FileAppend(A_YYYY "-" A_MM "-" A_DD " " A_Hour ":" A_Min ":" A_Sec " - " . message . "`n", logFile)
}
}
; =============================================================================
; FILE QUEUING
; =============================================================================
QueueFile(filename) {
global fileStates ; <--- Diese Zeile oben in der Funktion hinzufügen, falls sie fehlt
if InStr(filename, "tts_output_") {
fullPath := watchDir "\" . filename
; Wenn der Pfad in der Map ist (als "queued", "processing" oder Zeitstempel),
; ignorieren wir das Event.
if fileStates.Has(fullPath) {
return
}
fileStates[fullPath] := "queued"
Log("Queuing new file -> " . filename)
fileQueue.Push(fullPath)
}
}
ProcessExistingFiles() {
Log("Scanning for existing files...")
Loop Files, watchDir "\tts_output_*.txt" {
QueueFile(A_LoopFileName)
}
ScheduleQueueProcessing() ; Use the safe scheduler
}
; =============================================================================
; Instead of running logic inside the callback, we set a Timer.
; This allows the callback to finish instantly, unfreezing the thread.
ScheduleQueueProcessing() {
; Run ProcessQueue once (-1) after 10ms
SetTimer(ProcessQueue, -10)
}
; =============================================================================
; QUEUE PROCESSING LOOP
; =============================================================================
; --- Ganz oben im Skript ---
global processedHistory := Map()
CleanupZombies() {
global fileStates
if (fileStates.Count == 0)
return
toDelete := []
now := A_TickCount
for fullPath, timestamp in fileStates {
; WICHTIG: Wenn val ein Text ist ("queued" oder "processing"),
; überspringen wir die Zeitberechnung, da die Datei noch in Arbeit ist.
if !IsNumber(timestamp) {
continue
}
; 1. Versuche zu löschen, wenn die Datei noch da ist
if FileExist(fullPath) {
try {
FileDelete(fullPath)
; Wenn erfolgreich, markieren wir den Map-Eintrag zum Löschen
; aber erst nach einer Sicherheitsmarge von 10 Sekunden
if (now - timestamp > 10000) {
toDelete.Push(fullPath)
}
} catch {
; Noch gesperrt...
; Datei ist noch gesperrt (wahrscheinlich von Python)
; Wir lassen sie in der Map, damit sie nicht doppelt getippt wird.
}
} else {
; Datei ist physisch schon weg
if (now - timestamp > 10000) {
toDelete.Push(fullPath)
}
}
; 2. Aus der Map löschen, wenn Datei weg UND 10 Sek um
if !FileExist(fullPath) && (now - timestamp > 10000) {
toDelete.Push(fullPath)
}
; 3. Sicherheits-Timeout (5 Min), falls Datei niemals gelöscht werden kann
if (now - timestamp > 300000) {
toDelete.Push(fullPath)
}
}
for path in toDelete {
if FileExist(path){
fileStates.Delete(path)
}
}
}
; --- 2. Anpassung in ProcessQueue ---
ProcessQueue() {
global isProcessingQueue, fileQueue, fileStates
if (isProcessingQueue)
return
isProcessingQueue := true
while (fileQueue.Length > 0) {
local fullPath := fileQueue[1]
; fileQueue.RemoveAt(1) ; <--- WICHTIG: Sofort aus der Liste nehmen!
; Nur verarbeiten, wenn sie im Status "queued" ist
if (!fileStates.Has(fullPath) || fileStates[fullPath] != "queued") {
continue
}
; Status auf "processing" setzen
fileStates[fullPath] := "processing"
try {
; Stabilitäts-Check (Punkt 3 der Experten: Sicherstellen, dass Python fertig ist)
size1 := FileGetSize(fullPath), Sleep(100), size2 := FileGetSize(fullPath)
if (size1 != size2 || size1 == 0) {
fileStates[fullPath] := "queued" ; Zurücksetzen für nächsten Versuch
isProcessingQueue := false
return
}
content := Trim(FileRead(fullPath, "UTF-8"))
if (content != "") {
fileStates[fullPath] := A_TickCount
Log("Typing content from " . fullPath)
SendText(content)
; Erfolg! Status auf "done" setzen
; fileStates[fullPath] := "done"
; fileStates["last_processed_time_" . fullPath] := A_TickCount ; Für Debouncing
}
fileQueue.RemoveAt(1)
} catch as e {
Log("Error: " . e.Message)
fileStates[fullPath] := "queued" ; Bei Fehler zurück in Warteschlange
fileQueue.RemoveAt(1)
}
}
CleanupZombies() ; Hier nutzen wir weiterhin fileStates statt processedZombies
isProcessingQueue := false
} ; Ende von ProcessQueue()
; Hilfsfunktion für rückstandsloses Löschen
SecureDelete(filePath) {
Loop 100 { ; Versuche es bis zu 10-mal (ca. 1 Sekunde lang)
try {
if !FileExist(filePath)
return
FileDelete(filePath)
return ; Erfolg!
} catch {
Sleep(100) ; Warte 100ms, falls Datei noch gesperrt ist
}
}
Log("CRITICAL: Could not delete sensitive file after 10 attempts: " filePath)
}
; =============================================================================
; WATCHER LOGIC
; =============================================================================
WatchFolder(pFolder) {
global hDir
hDir := DllCall("CreateFile", "Str", pFolder, "UInt", 1, "UInt", 7, "Ptr", 0, "UInt", 3, "UInt", 0x42000000, "Ptr", 0, "Ptr")
if (hDir = -1) {
MsgBox("FATAL: Could not watch directory.", "Error", 16), ExitApp
}
ReArmWatcher()
}
ReArmWatcher() {
global hDir, pBuffer, pOverlapped, CompletionRoutineProc, watcherNeedsRearm
static notifyFilter := 0x11 ; 0x1 (Name) + 0x10 (LastWrite) - Better for file updates
DllCall("msvcrt\memset", "Ptr", pOverlapped.Ptr, "Int", 0, "Ptr", pOverlapped.Size)
local success := DllCall("ReadDirectoryChangesW", "Ptr", hDir, "Ptr", pBuffer, "UInt", pBuffer.Size, "Int", false, "UInt", notifyFilter, "Ptr", 0, "Ptr", pOverlapped, "Ptr", CompletionRoutineProc)
if (!success) {
Log("ReArmWatcher failed! Error: " . A_LastError)
watcherNeedsRearm := true
}
}
IOCompletionRoutine(dwErrorCode, dwNumberOfBytesTransfered, lpOverlapped) {
global pBuffer, watcherNeedsRearm
if (dwErrorCode != 0) {
Log("IOCompletionRoutine Error: " . dwErrorCode)
watcherNeedsRearm := true
return
}
if (dwNumberOfBytesTransfered > 0) {
local pCurrent := pBuffer.Ptr
Loop {
local NextEntryOffset := NumGet(pCurrent, 0, "UInt")
local Action := NumGet(pCurrent + 4, "UInt")
local FileName := StrGet(pCurrent + 12, NumGet(pCurrent + 8, "UInt") / 2, "UTF-16")
; Action 1 = Added, Action 3 = Modified
if (Action = 1 or Action = 3) {
QueueFile(FileName)
}
if (!NextEntryOffset)
break
pCurrent += NextEntryOffset
}
; --- CRITICAL CHANGE: DO NOT PROCESS HERE ---
; Instead of calling ProcessQueue() directly, we schedule it.
ScheduleQueueProcessing()
}
watcherNeedsRearm := true
}
type_watcher_keep_alive.sh¶
#!/bin/bash
# type_watcher_keep_alive.sh
# Stoppen: pkill -f type_watcher_keep_alive.sh
# --- PURPOSE OF THIS WRAPPER SCRIPT ---
#
# The main 'type_watcher.sh' script is generally stable, but has been
# observed to crash under specific, hard-to-reproduce circumstances,
# likely related to race conditions or heavy load (e.g., after processing
# many consecutive inputs).
#
# This wrapper script acts as a simple and robust "keep-alive" watchdog.
# It runs 'type_watcher.sh' in an infinite loop. If the main script
# ever crashes or exits unexpectedly, this watchdog will automatically
# restart it after a short delay. This ensures high availability without
# making the main script overly complex.
clear
# This script acts as a watchdog for type_watcher.sh.
# It runs it in an infinite loop, so if it ever crashes, it will be
# automatically restarted after a 1-second delay.
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
LOG_DIR="$SCRIPT_DIR/log"
LOGFILE="$LOG_DIR/type_watcher_keep_alive.sh"
CONFIG_FILE_1="$SCRIPT_DIR/config/settings.py"
CONFIG_FILE_2="$SCRIPT_DIR/config/settings_local.py"
# Initial timestamps
ts1_old=$(stat -c %Y "$CONFIG_FILE_1" 2>/dev/null || echo 0)
ts2_old=$(stat -c %Y "$CONFIG_FILE_2" 2>/dev/null || echo 0)
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOGFILE"
}
echo "Starting watchdog for type_watcher.sh"
log_message "Starting watchdog for type_watcher.sh"
while true; do
# Check if the process is running by looking for a process with the script's name.
# We use 'pgrep -f' and exclude the watchdog script itself.
# Check config changes
ts1_new=$(stat -c %Y "$CONFIG_FILE_1" 2>/dev/null || echo 0)
ts2_new=$(stat -c %Y "$CONFIG_FILE_2" 2>/dev/null || echo 0)
if [ "$ts1_old" != "$ts1_new" ] || [ "$ts2_old" != "$ts2_new" ]; then
log_message "Config changed - killing type_watcher for restart"
pkill -f "type_watcher.sh"
ts1_old=$ts1_new
ts2_old=$ts2_new
fi
if pgrep -f "type_watcher.sh" | grep -qv "$$"; then
# It's running, do nothing.
:
else
# It's not running, so it must have crashed. Start it.
log_message "WATCHDOG: 'type_watcher.sh' is not running. Starting it now."
log_message "$SCRIPT_DIR/type_watcher.sh"
echo log_message "WATCHDOG: 'type_watcher.sh' is not running. Starting it now "
echo $SCRIPT_DIR/type_watcher.sh
$SCRIPT_DIR/type_watcher.sh
fi
# Wait for a few seconds before checking again.
sleep 5
done