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>
127 lines
5.2 KiB
PowerShell
127 lines
5.2 KiB
PowerShell
<#
|
||
Сборка «Переводчика экрана».
|
||
|
||
.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 "Запущен. Значок в трее."
|
||
}
|