Переводчик экрана: наложение перевода поверх текста на экране
build / build (push) Failing after 10s
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>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
<#
|
||||
Сборка «Переводчика экрана».
|
||||
|
||||
.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 "Запущен. Значок в трее."
|
||||
}
|
||||
Reference in New Issue
Block a user