Exécution d’ABBYY FineReader Engine dans les services Azure
Exécution dans Azure Cloud Service
Cette section explique comment déployer une application FRE 12 pour Windows dans Azure Cloud Service. À titre d’exemple, un Worker qui traite des fichiers dans un conteneur d’un compte Azure Storage est présenté.Pour mettre en œuvre ce scénario, une licence en ligne se connectant au serveur de licences *.abbyy.com est utilisée.
ABBYY Licensing Service ne peut fonctionner qu’avec une seule licence en ligne à la fois.
Le déploiement de votre application dans Cloud Service comprend plusieurs étapes :
Configurer votre licence en ligne, votre machine locale et votre instance cloud en suivant les prérequis
Pour utiliser une licence en ligne sur tout système où ABBYY Licensing Service est installé, les conditions suivantes doivent être remplies :
Une connexion Internet active
Les connexions à *.abbyy.com sur le port 443 (HTTPS) doivent être autorisées
Le certificat racine GoDaddy de l’autorité de certification (il doit être installé dans le magasin de certificats Autorités de certification racines de confiance de l’ordinateur local. Consultez les informations détaillées sur le certificat sur le site web de GoDaddy.)
Avant de créer votre Cloud Service, assurez-vous que votre machine locale répond aux exigences suivantes :
Visual Studio 2019 et ses modules pour le développement d’applications Azure (consultez Fonctionnalités Azure dans Visual Studio ou utilisez Visual Studio Installer pour télécharger ces modules)
Émulateurs Cloud Service et stockage pour faciliter le débogage (consultez les informations ici et téléchargez-les via Visual Studio Installer)
Solution modèle pour Azure Cloud Service (extended preview) avec un seul projet de rôle Worker (consultez les informations ici)
Package NuGet pour utiliser Azure Storage et les conteneurs Blob : Azure.Storage.Blobs (téléchargez-le ici)
Wrapper ABBYY FineReader Engine pour .NET Framework 4.7 (dans le dossier C:\ProgramData\ABBYY\SDK\12\FineReader Engine\Inc.NET interops après une installation Developer)
Interface IFileWriter remplacée pour fonctionner avec des conteneurs Blob (consultez l’exemple ci-dessous)
Azure Storage Explorer (facultatif - téléchargez-le ici)
Les étapes préparatoires doivent être effectuées sur votre machine locale. En les réalisant, vous préparerez tous les paramètres et fichiers nécessaires au déploiement de votre application :
Créez deux archives contenant l’ABBYY FineReader Engine Library et le Licensing Service (par exemple, LibraryPackage.zip et LSPackage.zip). Vous pouvez générer automatiquement la liste des fichiers à l’aide du fichier FREngineDistribution.csv. Utilisez ABBYY FineReader Engine et License Server issus du même package ; sinon, la compatibilité n’est pas garantie.
Créez le compte de stockage Azure (frestorage dans cet article). Vous trouverez toutes les instructions nécessaires sur le site Azure.
Créez trois conteneurs Blob dans frestorage :
fre-lib - pour les fichiers d’ABBYY FineReader Engine
fre-input - pour les fichiers en entrée
fre-output - pour les résultats du traitement
Téléversez LibraryPackage.zip et LSPackage.zip dans le conteneur fre-lib par la méthode de votre choix (à l’aide de .NET, Powershell, d’un script Python ou des applications Azure Storage Explorer/Azure Portal).
À titre d’exemple, un projet WorkerRole est utilisé dans un environnement configuré. Tous les fichiers de configuration nécessaires (.csdef et Cloud.cscfg) sont générés automatiquement après la création de votre projet.
Déploiement d’ABBYY FineReader Engine vers Cloud Service
Pour déployer ABBYY FineReader Engine dans votre nouveau projet WorkerRole :
Spécifiez les paramètres de Cloud.cscfg selon vos besoins.
Spécifiez les paramètres du fichier .csdef :
(facultatif) les paramètres de votre WorkerRole
(facultatif) la taille de votre machine virtuelle
(obligatoire) le stockage local de votre rôle (section LocalStorage — dans cet article, le stockage nommé LocalStorage1). Définissez-le sur au moins 3 Go afin de garantir le déploiement correct du package ABBYY FineReader Engine.
(obligatoire) l’ordre de démarrage de votre rôle (utilisez les exemples de code et les paramètres de la section exemples de code)
Implémentez votre projet WorkerRole pour traiter les fichiers du conteneur fre-input et publier les résultats du traitement dans le conteneur fre-output :
la méthode OnStart pour préparer votre Cloud Service au fonctionnement. Cette méthode sert à initialiser l’Engine et à configurer le protocole TLS.
la méthode RunAsync pour traiter en boucle les fichiers récupérés du conteneur fre-input. Cette méthode détecte les fichiers dans le conteneur fre-input, les traite en mémoire, puis les place dans le conteneur fre-output (voir Processor.cs, IFileWriter.cs et Config.cs).
la méthode OnStop pour arrêter Cloud Service. Cette méthode sert à désinitialiser l’Engine.
Cette section comprend des exemples de code utilisés pour configurer l’ordre de démarrage des rôles et les paramètres d’ABBYY FineReader Engine :
CleanUpOnStart - supprime la version précédente d’ABBYY FineReader Engine et simplifie sa procédure de mise à jour (voir son contenu dans les fichiers CleanUpOnStart.cmd et CleanUpOnStart.ps1 ci-dessous).
PreparePoShModules - télécharge le SDK nécessaire pour que PowerShell fonctionne avec Azure Storage (voir son contenu dans les fichiers PreparePoShModules.cmd et PreparePoShModules.ps1 ci-dessous).
PrepareLibrary - téléverse et décompresse la bibliothèque ABBYY FineReader Engine depuis le conteneur fre-lib vers le stockage local (voir son contenu dans les fichiers PrepareLibrary.cmd et PrepareLibrary.ps1 ci-dessous).
PrepareLS - téléverse, décompresse et lance le Licensing Service depuis le conteneur fre-lib (voir son contenu dans les fichiers PrepareLS.cmd et PrepareLS.ps1 ci-dessous).
Les scripts listés ci-dessus doivent être exécutés exactement dans l’ordre indiqué, avant le lancement de votre application. Pour personnaliser l’ordre d’exécution des scripts, spécifiez les attributs suivants :
taskType=“simple” - les tâches sont exécutées de manière synchrone, une à la fois.
executionContext=“elevated” - exécution d’un script de démarrage avec des droits administrateur (requise pour pouvoir installer une application et exécuter LicensingService.exe)
Ainsi, un dossier que Cloud Service pourra gérer sera créé. Ce dossier est utilisé pour téléverser ABBYY FineReader Engine à l’aide des scripts listés ci-dessus et charger FREngine.dll en vue de son utilisation ultérieure dans l’exemple.
CleanUpOnStart.cmd
rem Script pour nettoyer les packages précédents d’ABBYY FineReader Engine et du Licensing Serviceecho Cleaning up before launching service >> ".\CleanUpOnStart.log" 2>&1PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\CleanUpOnStart.ps1 >> ".\CleanUpOnStart.log" 2>&1echo Cleaning up beafore launching service. >> ".\CleanUpOnStart.log" 2>&1exit /B %errorlevel%
CleanUpOnStart.ps1
Write-Host "CleanUpOnStart.ps1 started...";# chemin vers le stockage local (voir ServiceDefinition.csdef)$local_storage_name = "LocalStorage1";[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.WindowsAzure.ServiceRuntime");$local_storage_path = ([Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetLocalResource($local_storage_name)).RootPath.TrimEnd('\\');# arrêt du processus Licensing Service s'il était en cours d'exécution$processes = Get-Process;foreach( $process in $processes ) { if( $process.Name -eq "LicesingService" ) { Stop-Process $process; }}# nettoyage et recréation du dossier fre_packages$destination_path = Join-Path -Path $local_storage_path -ChildPath 'fre_packages';if( Test-Path -Path $destination_path ) { Remove-Item $destination_path -Recurse -Force;}New-Item -ItemType Directory -Path $destination_path;# nettoyage et recréation du dossier Input$destination_path = Join-Path -Path $local_storage_path -ChildPath 'Input';if( Test-Path -Path $destination_path ) { Remove-Item $destination_path -Recurse -Force;}New-Item -ItemType Directory -Path $destination_path;# nettoyage et recréation du dossier Results$destination_path = Join-Path -Path $local_storage_path -ChildPath 'Results';if( Test-Path -Path $destination_path ) { Remove-Item $destination_path -Recurse -Force;}New-Item -ItemType Directory -Path $destination_path;Write-Host ("Resource folder was cleaned up.");
PreparePoShModules.cmd
rem Script pour préparer les modules afin de pouvoir se connecter au compte de stockage et télécharger les packages ABBYY FineReader Engine et Licensing Serviceecho Preparing PoSh Modules >> ".\PreparePoShModules.log" 2>&1PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\PreparePoShModules.ps1 >> ".\PreparePoShModules.log" 2>&1echo PoSh Modules wer prepared. >> ".\PreparePoShModules.log" 2>&1exit /B %errorlevel%
PreparePoShModules.ps1
# installation des modulesWrite-Host "PreparePoShModules.ps1 started...";# installation du fournisseur de packages NuGet pour télécharger les modules AzureWrite-Host "Installing NuGet package provider...";Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force;# installation du module Azure Storage pour utiliser l’objet blobWrite-Host "Installing Az.Storage module...";Install-Module -Name Az.Storage -Scope CurrentUser -Repository PSGallery -Force -AllowClobber;Write-Host ("PoSh modules were prepared.");
PrepareLibrary.cmd
rem Script pour préparer le package ABBYY FineReader Engineecho Preparing Library >> ".\PrepareLibrary.log" 2>&1PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\PrepareLibrary.ps1 >> ".\PrepareLibrary.log" 2>&1echo Library was prepared. >> ".\PrepareLibrary.log" 2>&1exit /B %errorlevel%
PrepareLibrary.ps1
Write-Host "PrepareLibrary.ps1 started...";# conteneur avec les packages ABBYY FineReader Engine et Licensing Service$container_name = 'fre-lib';# chaîne de connexion au compte de stockage$connection_string = "<connection_string_to_frestorage>";Write-Host "Configuration";Write-Host ("Container name: " + $container_name);Write-Host ("Connection string: " + $storage_account);# connexion à l'objet de stockageWrite-Host ("Connecting to storage account...");$storage_account = New-AzStorageContext -ConnectionString $connection_string;# récupération des blobs (c'est-à-dire des fichiers)Write-Host ("Getting blobs from container...");$fre_blobs = Get-AzStorageBlob -Container $container_name -Context $storage_account;# chemin d'accès au stockage local (voir ServiceDefinition.csdef)$local_storage_name = "LocalStorage1";[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.WindowsAzure.ServiceRuntime");$local_storage_path = ([Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetLocalResource($local_storage_name)).RootPath.TrimEnd('\\');$destination_path = Join-Path -Path $local_storage_path -ChildPath 'fre_packages';# téléchargement du seul blob requisWrite-Host ("Downloading FRE package from container...");foreach( $blob in $fre_blobs ) { Write-Host $blob.Name; if( $blob.Name -eq "LibraryPackage.zip" ) { Write-Host ("Downloading blob: " + $blob.Name + "to " + $destination_path); Get-AzStorageBlobContent -Container $container_name -Blob $blob.Name ` -Destination (Join-Path -Path $destination_path -ChildPath $blob.Name) -Context $storage_account; } else { Write-Host ("Downloading blob " + $blob.Name + " was skipped."); }}# décompression du package téléchargé (l'ancienne bibliothèque sera écrasée de force)Write-Host ("Unzipping library package...");Expand-Archive -Path (Join-Path -Path $destination_path -ChildPath "LibraryPackage.zip") ` -DestinationPath (Join-Path -Path $destination_path -ChildPath "LibraryPackage") -Force;Write-Host ("Library package was prepared.");
PrepareLS.cmd
rem Script pour préparer le package Licensing Service et démarrer LicensingService.exeecho Preparing LS >> ".\PrepareLS.log" 2>&1PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\PrepareLS.ps1 >> ".\PrepareLS.log" 2>&1echo LS was prepared. >> ".\PrepareLS.log" 2>&1exit /B %errorlevel%
PrepareLS.ps1
Write-Host "Démarrage de PrepareLS.ps1...";# conteneur avec les packages ABBYY FineReader Engine et Licensing Service$container_name = 'fre-lib';# chaîne de connexion du compte de stockage$connection_string = "<connection_string_to_frestorage>";Write-Host "Configuration";Write-Host ("Nom du conteneur : " + $container_name);Write-Host ("Chaîne de connexion : " + $storage_account);# connexion à l’objet de stockageWrite-Host ("Connexion au compte de stockage...");$storage_account = New-AzStorageContext -ConnectionString $connection_string;# récupération des blobs (essentiellement des fichiers)Write-Host ("Récupération des blobs depuis le conteneur...");$fre_blobs = Get-AzStorageBlob -Container $container_name -Context $storage_account;# chemin vers le stockage local (voir ServiceDefinition.csdef)$local_storage_name = "LocalStorage1";[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.WindowsAzure.ServiceRuntime");$local_storage_path = ([Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetLocalResource($local_storage_name)).RootPath.TrimEnd('\\');$destination_path = Join-Path -Path $local_storage_path -ChildPath 'fre_packages'; # téléchargement du package Licensing ServiceWrite-Host ("Téléchargement du package Licensing Service depuis le conteneur...");foreach( $blob in $fre_blobs ) { Write-Host $blob.Name; if( $blob.Name -eq "LSPackage.zip" ) { Write-Host ("Téléchargement du blob : " + $blob.Name + " vers " + $destination_path); Get-AzStorageBlobContent -Container $container_name -Blob $blob.Name ` -Destination (Join-Path -Path $destination_path -ChildPath $blob.Name) -Context $storage_account; } else { Write-Host ("Le téléchargement du blob " + $blob.Name + " a été ignoré."); }}# décompression du package téléchargéWrite-Host ("Décompression du package de bibliothèque...");Expand-Archive -Path (Join-Path -Path $destination_path -ChildPath "LSPackage.zip") ` -DestinationPath (Join-Path -Path $destination_path -ChildPath "LSPackage") -Force;# création des dossiers pour les licences$program_data_path = [System.Environment]::ExpandEnvironmentVariables("%programdata%");$path = Join-Path -Path $program_data_path -ChildPath "ABBYY\SDK\12\Licenses";New-Item -ItemType Directory -Path $path -Force;# démarrage de Licensing Service en mode autonomeWrite-Host ("Démarrage de Licensing Service...");$licensing_service_app = Join-Path -Path $destination_path -ChildPath "LSPackage\LicensingService.exe";Start-Process -FilePath $licesing_service_app -ArgumentList "/standalone";Write-Host ("Le package LS a été préparé et LS a été démarré.");
Implémentation de l’interface IFileWriter
namespace WorkerRole1.Engine{ public class FileWriter : FREngine.IFileWriter, IDisposable { public FileWriter(string _resultBlobName, string _fileExtension) { resultBlobName = _resultBlobName; fileExtension = _fileExtension; } public void Open(string fileName, ref int bufferSize) { stream = new MemoryStream(); } public void Write(byte[] data) { stream.Write(data, 0, data.Length); } public void Close() { // Création d’une connexion à un nouveau blob dans <OutputContainerName> // Le résultat du traitement y sera stocké BlobClient resultBlobClient = new BlobClient(Config.ConnectionString, Config.OutputContainerName, resultBlobName + fileExtension); // Remplacer le fichier existant resultBlobClient.DeleteIfExists(); // Définir la position à 0 pour écrire le fichier depuis le début stream.Position = 0; resultBlobClient.Upload(stream); stream.Close(); } public void Dispose() { // Fermer le flux mémoire lors de la libération pour pouvoir y accéder après l’écriture des données stream.Close(); } private string resultBlobName; private string fileExtension; private MemoryStream stream; }}
Exemple de traitement des fichiers avec ABBYY FineReader Engine
namespace WorkerRole1{ public class WorkerRole : RoleEntryPoint { private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false); Processor processor = new Processor(); public override void Run() { Trace.TraceInformation("WorkerRole1 est en cours d’exécution"); try { this.RunAsync(this.cancellationTokenSource.Token).Wait(); } finally { this.runCompleteEvent.Set(); } } public override bool OnStart() { // Définir le nombre maximal de connexions simultanées ServicePointManager.DefaultConnectionLimit = 12; // Définir TLS sur 1.2 pour pouvoir se connecter au compte de stockage System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; // Pour plus d’informations sur la gestion des changements de configuration // consultez la rubrique MSDN à l’adresse https://go.microsoft.com/fwlink/?LinkId=166357 bool result = base.OnStart(); Trace.TraceInformation("WorkerRole1 a démarré"); processor.LoadEngine(); return result; } public override void OnStop() { processor.UnloadEngine(); Trace.TraceInformation("WorkerRole1 est en cours d’arrêt"); this.cancellationTokenSource.Cancel(); this.runCompleteEvent.WaitOne(); base.OnStop(); Trace.TraceInformation("WorkerRole1 s’est arrêté"); } private async Task RunAsync(CancellationToken cancellationToken) { // TODO : remplacez ce qui suit par votre propre logique while (!cancellationToken.IsCancellationRequested) { Trace.TraceInformation("En cours de traitement"); try { // Créer le conteneur et renvoyer un objet client de conteneur BlobContainerClient inputContainerClient = new BlobContainerClient(Config.ConnectionString, Config.InputContainerName); foreach (BlobItem blobItem in inputContainerClient.GetBlobs()) { Trace.TraceInformation("\t" + blobItem.Name); BlobClient blobClient = new BlobClient(Config.ConnectionString, Config.InputContainerName, blobItem.Name); Trace.TraceInformation("\t Téléchargement du blob en mémoire : " + blobItem.Name); MemoryStream memoryStream = new MemoryStream(); blobClient.DownloadTo(memoryStream); Trace.TraceInformation("\t Traitement dans FRE : " + blobItem.Name); string resultBlobName = processor.ProcessImage(memoryStream, blobItem.Name); Trace.TraceInformation("\t Nom du blob de résultat dans le conteneur de sortie : " + resultBlobName); Trace.TraceInformation("\t Suppression du conteneur d’entrée : " + blobItem.Name); blobClient.Delete(); } } catch (Exception error) { Trace.TraceInformation("erreur : " + error.Message); } await Task.Delay(1000); } } }}
Processor.cs
namespace WorkerRole1{ class Processor : IDisposable { EngineLoader engineLoader = null; private void displayMessage(string text) { File.AppendAllText(".\\FRE.log", text + "\n"); Trace.TraceInformation("\t" + text ); } private void loadProfile() { engineLoader.Engine.LoadPredefinedProfile("DocumentConversion_Accuracy"); } private void setupFREngine() { displayMessage("Loading predefined profile..."); loadProfile(); } public void LoadEngine() { try { if (engineLoader == null) { // Le même EngineLoader que dans l’exemple Hello engineLoader = new EngineLoader(); } setupFREngine(); } catch (Exception error) { displayMessage("error: " + error.Message); } } public void UnloadEngine() { try { if (engineLoader != null) { engineLoader.Dispose(); engineLoader = null; } } catch (Exception error) { displayMessage("error: " + error.Message); } } public string ProcessImage( MemoryStream inputMemoryStream, string inputBlobName ) { FRDocument document = engineLoader.Engine.CreateFRDocument(); string resultBlobName = ""; try { document.PageFlushingPolicy = FREngine.PageFlushingPolicyEnum.PFP_KeepInMemory; // Ajouter le fichier image au document displayMessage("Loading image..."); IntPtr handle = Marshal.AllocHGlobal(inputMemoryStream.GetBuffer().Length); Marshal.Copy(inputMemoryStream.GetBuffer(), 0, handle, inputMemoryStream.GetBuffer().Length); document.AddImageFileFromMemory(handle.ToInt64(), null, null); // Reconnaître le document displayMessage("Recognizing..."); document.Process(null); // Enregistrer les résultats displayMessage("Saving results..."); // Enregistrer les résultats au format PDF avec le scénario « balanced » // FREngine.PDFExportParams pdfParams = engineLoader.Engine.CreatePDFExportParams(); // pdfParams.Scenario = FREngine.PDFExportScenarioEnum.PES_Balanced; WorkerRole1.Engine.FileWriter fileWriter = new WorkerRole1.Engine.FileWriter(inputBlobName, ".pdf"); resultBlobName = inputBlobName + ".pdf"; document.ExportToMemory(fileWriter, FREngine.FileExportFormatEnum.FEF_PDF, null); } catch (Exception error) { displayMessage("error: " + error.Message); } finally { // Fermer le document document.Close(); } return resultBlobName; } public void Dispose() { UnloadEngine(); } }}
Config.cs
class Config{ // Stockage local tel que défini dans ServiceDefinition.csdef private static string LocalStorageName = "LocalStorage1"; // Chaîne de connexion au conteneur Blob public static readonly string ConnectionString = "<connection_string_to_frestorage>"; // Noms des conteneurs d’entrée et de sortie dans votre stockage public static readonly string InputContainerName = "fre-input"; public static readonly string OutputContainerName = "fre-output"; // Retourne le Customer Project ID pour ABBYY FineReader Engine public static String GetCustomerProjectId() { return "<Your_Customer_Project_ID>"; } // Retourne le nom du jeton de licence public static String GetLicenseTokenName() { return "<Token_number>.ABBYY.ActivationToken"; } // Retourne le mot de passe de la licence public static String GetLicensePassword() { return "<Your_Online_License_token_password>"; } // Retourne le dossier du moteur public static String GetEngineFolder() { string engineSubfolder = "fre_packages\\LibraryPackage\\Bin64"; string engineDllFolder = Path.Combine(RoleEnvironment.GetLocalResource(LocalStorageName).RootPath, engineSubfolder); return engineDllFolder; }}