Files
screen-translator/build.ps1
T
gusadmin 7c6b182402
build / build (push) Failing after 10s
Переводчик экрана: наложение перевода поверх текста на экране
Программа накладывает русский перевод прямо на английские надписи —
как переводчик по фото, только для монитора. Работает с играми,
интерфейсами и документами: распознаёт кадр, собирает текст в связные
блоки, переводит и рисует плашки под цвет фона.

Распознавание — встроенный Windows.Media.Ocr, офлайн. Перевод — цепочка
движков с автопереключением (google, lingva, mymemory), потому что до
разных сервисов из разных сетей достучаться получается по-разному.

Собирается без .NET SDK: Roslyn из Build Tools плюс сборки Framework 4.8,
которые уже есть в системе. На выходе один exe без установщика.

Главная сложность оказалась не в переводе, а в том, чтобы понять, где на
экране связный текст, а где отдельные надписи. Классические алгоритмы
сегментации решают это по межстрочному зазору, но в плотном интерфейсе он
одинаков у строк абзаца и у соседних контролов. Поэтому решение принимает
ансамбль признаков: фон, граница между строками, заполненность строки,
кегль. Надписи на чужом языке отсеиваются тремя уровнями — форма слова,
вторая модель распознавания и частоты буквенных пар.

Чтобы правки эвристик не оценивались на глаз, в tools лежат эталонные
кадры и RenderTest: прогон конвейера без окна с подсчётом попаданий.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:54:02 +03:00

127 lines
5.2 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
Сборка «Переводчика экрана».
.NET SDK не нужен: используется Roslyn из VS Build Tools и сборки .NET Framework 4.8,
которые уже есть в Windows. На выходе — один самодостаточный exe.
Использование: .\build.ps1 собрать в .\dist
.\build.ps1 -Run собрать и запустить
#>
[CmdletBinding()]
param(
[switch]$Run,
[switch]$Test, # дополнительно собрать RenderTest.exe — прогон конвейера без окна, результат в PNG
[string]$OutDir = "$PSScriptRoot\dist"
)
$ErrorActionPreference = 'Stop'
function Find-Csc {
$candidates = @(
"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\Roslyn\csc.exe",
"C:\Program Files\Microsoft Visual Studio\2022\*\MSBuild\Current\Bin\Roslyn\csc.exe",
"C:\Program Files (x86)\Microsoft Visual Studio\2022\*\MSBuild\Current\Bin\Roslyn\csc.exe",
"C:\Program Files (x86)\MSBuild\14.0\Bin\csc.exe"
)
foreach ($c in $candidates) {
$hit = Get-Item $c -ErrorAction SilentlyContinue | Select-Object -First 1
if ($hit) { return $hit.FullName }
}
# запасной вариант — компилятор из самой Windows (старый C#, но соберёт)
$fw = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
if (Test-Path $fw) { return $fw }
throw "Не найден компилятор C#. Поставьте Visual Studio Build Tools 2022."
}
function Find-WinMd {
$root = "C:\Program Files (x86)\Windows Kits\10\UnionMetadata"
if (-not (Test-Path $root)) { throw "Не найден Windows SDK (UnionMetadata) — без него нет доступа к Windows OCR." }
# рядом лежит папка Facade с заглушкой — берём только версионные каталоги (10.0.x)
$ver = Get-ChildItem $root -Directory |
Where-Object { $_.Name -match '^\d+\.' -and (Test-Path (Join-Path $_.FullName 'Windows.winmd')) } |
Sort-Object { [version]$_.Name } -Descending | Select-Object -First 1
if (-not $ver) { throw "В Windows SDK нет версионного Windows.winmd." }
return (Join-Path $ver.FullName 'Windows.winmd')
}
$csc = Find-Csc
$winmd = Find-WinMd
$fw = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319"
$gac = "C:\Windows\Microsoft.NET\assembly\GAC_MSIL"
function Gac($name) {
$hit = Get-ChildItem (Join-Path $gac $name) -Recurse -Filter "$name.dll" -ErrorAction SilentlyContinue |
Select-Object -First 1
if (-not $hit) { throw "Нет сборки $name в GAC." }
return $hit.FullName
}
$refs = @(
"$fw\mscorlib.dll"
"$fw\System.dll"
"$fw\System.Core.dll"
"$fw\System.Drawing.dll"
"$fw\System.Windows.Forms.dll"
"$fw\System.Runtime.dll"
"$fw\System.Web.dll"
(Gac 'System.Web.Extensions')
(Gac 'System.Runtime.WindowsRuntime')
$winmd
)
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
$exe = Join-Path $OutDir 'ScreenTranslator.exe'
# запущенная копия держит файл, и линковка падает с невнятной ошибкой
$running = Get-Process ScreenTranslator -ErrorAction SilentlyContinue
if ($running) {
Write-Host "Останавливаю запущенный ScreenTranslator (PID $($running.Id))…"
$running | Stop-Process -Force
Start-Sleep -Milliseconds 400
}
$sources = Get-ChildItem "$PSScriptRoot\src" -Filter *.cs | ForEach-Object { $_.FullName }
Write-Host "csc : $csc"
Write-Host "winmd : $winmd"
Write-Host "исходники: $($sources.Count) файлов"
Write-Host "выход : $exe"
Write-Host ''
$cscArgs = @(
'/nologo'
'/target:winexe'
'/platform:x64'
'/optimize+'
'/langversion:latest'
"/out:$exe"
"/win32manifest:$PSScriptRoot\app.manifest"
) + ($refs | ForEach-Object { "/r:$_" }) + $sources
& $csc @cscArgs
if ($LASTEXITCODE -ne 0) { throw "Сборка не удалась (код $LASTEXITCODE)." }
Write-Host ("`nГотово: {0} ({1:N0} КБ)" -f $exe, ((Get-Item $exe).Length / 1KB))
if ($Test) {
$testExe = Join-Path $OutDir 'RenderTest.exe'
# тот же код, но с консольной точкой входа вместо трея
$testSources = ($sources | Where-Object { $_ -notlike '*\Program.cs' }) + "$PSScriptRoot\tools\RenderTest.cs"
$testArgs = @(
'/nologo', '/target:exe', '/platform:x64', '/optimize+', '/langversion:latest'
"/out:$testExe"
"/win32manifest:$PSScriptRoot\app.manifest" # без него захват придёт в логических пикселях
) + ($refs | ForEach-Object { "/r:$_" }) + $testSources
& $csc @testArgs
if ($LASTEXITCODE -ne 0) { throw "Сборка теста не удалась (код $LASTEXITCODE)." }
Write-Host ("Готово: {0} ({1:N0} КБ)" -f $testExe, ((Get-Item $testExe).Length / 1KB))
}
if ($Run) {
Get-Process ScreenTranslator -ErrorAction SilentlyContinue | Stop-Process -Force
Start-Sleep -Milliseconds 300
Start-Process $exe
Write-Host "Запущен. Значок в трее."
}