@@ -8,8 +8,8 @@ namespace CompatBot.Ocr.Backend;
88
99internal class Tesseract : BackendBase
1010{
11- private TesseractEngine engine ;
12- private static readonly SemaphoreSlim limiter = new ( 1 , 1 ) ;
11+ private TesseractEngine engine = null ! ;
12+ private static readonly SemaphoreSlim Limiter = new ( 1 , 1 ) ;
1313
1414 public override string Name => "tesseract" ;
1515
@@ -28,34 +28,14 @@ public override async Task<bool> InitializeAsync(CancellationToken cancellationT
2828 return false ;
2929 }
3030
31- var engModelPath = Path . Combine ( ModelCachePath , "eng.traineddata" ) ;
32- if ( ! File . Exists ( engModelPath ) )
33- {
34- try
35- {
36- using var client = HttpClientFactory . Create ( new CompressionMessageHandler ( ) ) ;
37- // existing repos: tessdata_fast, tessdata, tessdata_best
38- const string uri = "https://github.com/tesseract-ocr/tessdata_best/raw/refs/heads/main/eng.traineddata" ;
39- await using var response = await client . GetStreamAsync ( uri , cancellationToken ) . ConfigureAwait ( false ) ;
40- await using var file = File . Open ( engModelPath , new FileStreamOptions
41- {
42- Mode = FileMode . Create ,
43- Access = FileAccess . Write ,
44- Share = FileShare . None ,
45- Options = FileOptions . Asynchronous | FileOptions . SequentialScan ,
46- } ) ;
47- await response . CopyToAsync ( file , cancellationToken ) . ConfigureAwait ( false ) ;
48- }
49- catch ( Exception e )
50- {
51- Config . Log . Error ( e , "Failed to download model data" ) ;
52- return false ;
53- }
54- }
31+ var modelsAvailable = await EnsureModelIsCached ( "eng" , cancellationToken ) . ConfigureAwait ( false )
32+ && await EnsureModelIsCached ( "rus" , cancellationToken ) . ConfigureAwait ( false ) ;
33+ if ( ! modelsAvailable )
34+ return false ;
5535
5636 try
5737 {
58- engine = new ( ModelCachePath , "eng" , EngineMode . Default ) ;
38+ engine = new ( ModelCachePath , "eng+rus " , EngineMode . Default ) ;
5939 }
6040 catch ( Exception e )
6141 {
@@ -84,15 +64,15 @@ public override async Task<bool> InitializeAsync(CancellationToken cancellationT
8464 img . Dispose ( ) ;
8565 img = img2 ;
8666 }
87- await limiter . WaitAsync ( Config . Cts . Token ) . ConfigureAwait ( false ) ;
67+ await Limiter . WaitAsync ( Config . Cts . Token ) . ConfigureAwait ( false ) ;
8868 try
8969 {
9070 using var page = engine . Process ( img ) ;
9171 return ( page . GetText ( ) ?? "" , page . GetMeanConfidence ( ) ) ;
9272 }
9373 finally
9474 {
95- limiter . Release ( ) ;
75+ Limiter . Release ( ) ;
9676 }
9777 }
9878 finally
@@ -107,6 +87,35 @@ public override void Dispose()
10787 engine . Dispose ( ) ;
10888 }
10989
90+ private async ValueTask < bool > EnsureModelIsCached ( string lang , CancellationToken cancellationToken )
91+ {
92+ var modelPath = Path . Combine ( ModelCachePath , $ "{ lang } .traineddata") ;
93+ if ( File . Exists ( modelPath ) )
94+ return true ;
95+
96+ try
97+ {
98+ using var client = HttpClientFactory . Create ( new CompressionMessageHandler ( ) ) ;
99+ // existing repos: tessdata_fast, tessdata, tessdata_best
100+ var uri = $ "https://github.com/tesseract-ocr/tessdata_best/raw/refs/heads/main/{ lang } .traineddata";
101+ await using var response = await client . GetStreamAsync ( uri , cancellationToken ) . ConfigureAwait ( false ) ;
102+ await using var file = File . Open ( modelPath , new FileStreamOptions
103+ {
104+ Mode = FileMode . Create ,
105+ Access = FileAccess . Write ,
106+ Share = FileShare . None ,
107+ Options = FileOptions . Asynchronous | FileOptions . SequentialScan ,
108+ } ) ;
109+ await response . CopyToAsync ( file , cancellationToken ) . ConfigureAwait ( false ) ;
110+ }
111+ catch ( Exception e )
112+ {
113+ Config . Log . Error ( e , $ "Failed to download model data for language { lang } ") ;
114+ return false ;
115+ }
116+ return true ;
117+ }
118+
110119 private enum RotationDirection
111120 {
112121 Clockwise = 1 ,
0 commit comments