Ejecución de ABBYY FineReader Engine en Azure Services
Ejecución en Azure Cloud Service
Esta sección proporciona instrucciones sobre cómo desplegar una aplicación FRE 12 para Windows en Azure Cloud Service. Como ejemplo, se muestra un Worker que procesa archivos mediante un contenedor en una cuenta de Azure Storage.Para implementar este escenario, se utiliza una licencia en línea que se conecta al servidor de licencias de *.abbyy.com.
El servicio de licencias de ABBYY solo puede funcionar con una licencia en línea al mismo tiempo.
Desplegar su aplicación en Cloud Service incluye varios pasos:
Configurar su licencia en línea, el equipo local y la instancia en la nube siguiendo los requisitos previos
un archivo de token de licencia para la licencia en línea
la contraseña del archivo de token de licencia
Debe cumplir las siguientes condiciones para usar una licencia en línea en cualquier lugar donde esté instalado el servicio de licencias de ABBYY:
Conexión activa a Internet
Conexiones permitidas a *.abbyy.com por el puerto 443 (HTTPS)
Certificado raíz de GoDaddy para la entidad de certificación (Debe estar instalado en el almacén de certificados Trusted Root Certification Authorities de la versión del equipo local. Consulte la información detallada sobre el certificado en el sitio web de GoDaddy).
Antes de crear su Cloud Service, use la siguiente especificación para preparar su equipo local:
Visual Studio 2019 y sus módulos para desarrollar aplicaciones para Azure (consulte Funcionalidades de Azure en Visual Studio o use Visual Studio Installer para descargar dichos módulos)
Emuladores de Cloud Service y almacenamiento para facilitar la depuración (consulte la información aquí y descárguelos mediante Visual Studio Installer)
Solución de plantilla para Azure Cloud Service (versión preliminar extendida) con un único proyecto de rol Worker (consulte la información aquí)
Paquete NuGet para trabajar con Azure Storage y contenedores Blob: Azure.Storage.Blobs (descárguelo aquí)
Wrapper de ABBYY FineReader Engine para .NET Framework 4.7 (en la carpeta C:\ProgramData\ABBYY\SDK\12\FineReader Engine\Inc.NET interops tras una instalación Developer)
Interfaz IFileWriter con implementación personalizada para trabajar con contenedores Blob (consulte el ejemplo a continuación)
Azure Storage Explorer (opcional; descárguelo aquí)
La instancia de Cloud Service se utiliza para almacenar su proyecto WorkerRole en Azure. Use la siguiente especificación para configurar esta instancia:
versión de .NET Framework de su servicio
PowerShell para implementar ABBYY FineReader Engine
NuGet 2.8.5.201 o superior para cargar el SDK de Azure
SDK de Azure para trabajar con una cuenta de Azure Storage mediante PowerShell
Los pasos preparatorios deben realizarse en su equipo local. Al completar estos pasos, preparará toda la configuración y los archivos necesarios para comenzar el despliegue de su aplicación:
Cree dos archivos comprimidos con ABBYY FineReader Engine Library y el servicio de licencias (por ejemplo, LibraryPackage.zip y LSPackage.zip). Puede generar la lista de archivos automáticamente con ayuda del archivo FREngineDistribution.csv. Use ABBYY FineReader Engine y el servidor de licencias del mismo paquete; de lo contrario, no se garantiza la compatibilidad.
Cree la cuenta de Azure Storage (frestorage en este artículo). Puede encontrar todas las instrucciones necesarias en el sitio web de Azure.
Cree tres contenedores de blobs dentro de frestorage:
fre-lib - para los archivos de ABBYY FineReader Engine
fre-input - para los archivos de entrada
fre-output - para los resultados del procesamiento
Cargue LibraryPackage.zip y LSPackage.zip en el contenedor fre-lib de la forma que le resulte más conveniente (mediante .NET, PowerShell, un script de Python o las aplicaciones Azure Storage Explorer/Azure Portal).
Como ejemplo, se utiliza un proyecto WorkerRole para trabajar en un entorno configurado. Todos los archivos de configuración necesarios (.csdef y Cloud.cscfg) se generan automáticamente después de crear su proyecto.
Implementación de ABBYY FineReader Engine en Cloud Service
Para implementar ABBYY FineReader Engine en su nuevo proyecto de WorkerRole:
Especifique los parámetros de Cloud.cscfg según sea necesario.
Especifique los parámetros del archivo .csdef:
(opcional) la configuración de su WorkerRole
(opcional) el tamaño de su máquina virtual
(obligatorio) el almacenamiento local de su rol (sección LocalStorage; en este artículo, el almacenamiento denominado LocalStorage1). Configúrelo con al menos 3 GB para garantizar que el paquete de ABBYY FineReader Engine se implemente correctamente.
(obligatorio) el orden de inicio de su rol (use los ejemplos de código y la configuración de la sección code samples)
Implemente su proyecto de WorkerRole para procesar archivos del contenedor fre-input y publicar los resultados del procesamiento en el contenedor fre-output:
el método OnStart para preparar su Cloud Service para su funcionamiento. Este método se utiliza para inicializar el Engine y configurar el protocolo TLS.
el método RunAsync para procesar cíclicamente los archivos tomados del contenedor fre-input. Este método detecta los archivos del contenedor fre-input, los procesa en memoria y los coloca en el contenedor fre-output (consulte Processor.cs, IFileWriter.cs y Config.cs).
el método OnStop para finalizar la ejecución de Cloud Service. Este método se utiliza para desinicializar el Engine.
Esta sección incluye ejemplos de código que se utilizan para configurar el orden de inicio de los roles y los ajustes de ABBYY FineReader Engine:
CleanUpOnStart - elimina la versión anterior de ABBYY FineReader Engine y simplifica el procedimiento de actualización (consulte su contenido en los archivos CleanUpOnStart.cmd y CleanUpOnStart.ps1 a continuación).
PreparePoShModules - descarga el SDK necesario para que PowerShell funcione con Azure Storage (consulte su contenido en los archivos PreparePoShModules.cmd y PreparePoShModules.ps1 a continuación).
PrepareLibrary - carga y descomprime la ABBYY FineReader Engine Library desde el contenedor fre-lib en el almacenamiento local (consulte su contenido en los archivos PrepareLibrary.cmd y PrepareLibrary.ps1 a continuación).
PrepareLS - carga, descomprime e inicia el servicio de licencias desde el contenedor fre-lib (consulte su contenido en los archivos PrepareLS.cmd y PrepareLS.ps1 a continuación).
Es necesario que los scripts indicados anteriormente se ejecuten exactamente en el orden propuesto, antes de iniciar la aplicación. Para personalizar el orden de ejecución de los scripts, especifique los siguientes atributos:
taskType=“simple” - las tareas se ejecutan de forma síncrona, una por una.
executionContext=“elevated” - ejecución de un script de inicio con derechos de administrador (necesario para poder instalar cualquier aplicación y ejecutar LicensingService.exe)
Como resultado, se creará una carpeta que Cloud Service podrá administrar. Esta carpeta se usa para cargar ABBYY FineReader Engine mediante los scripts indicados anteriormente y cargar FREngine.dll para su uso posterior en el ejemplo.
CleanUpOnStart.cmd
rem Script para limpiar los paquetes anteriores de ABBYY FineReader Engine y 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...";# ruta al almacenamiento local (consulte 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('\\');# detener el proceso del servicio de licencias si estaba en ejecución$processes = Get-Process;foreach( $process in $processes ) { if( $process.Name -eq "LicesingService" ) { Stop-Process $process; }}# limpiar y volver a crear la carpeta 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;# limpiar y volver a crear la carpeta 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;# limpiar y volver a crear la carpeta 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 para preparar los módulos necesarios para conectarse a la cuenta de almacenamiento y descargar los paquetes de ABBYY FineReader Engine y del servicio de licenciasecho 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
# instalar módulosWrite-Host "PreparePoShModules.ps1 started...";# instalar el proveedor de paquetes NuGet para descargar módulos de AzureWrite-Host "Installing NuGet package provider...";Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force;# instalar el módulo Az.Storage para trabajar con blobsWrite-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 para preparar el paquete de 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...";# contenedor con los paquetes de ABBYY FineReader Engine y del servicio de licencias$container_name = 'fre-lib';# cadena de conexión de la cuenta de almacenamiento $connection_string = "<connection_string_to_frestorage>";Write-Host "Configuration";Write-Host ("Container name: " + $container_name);Write-Host ("Connection string: " + $storage_account);# conexión al objeto de almacenamientoWrite-Host ("Connecting to storage account...");$storage_account = New-AzStorageContext -ConnectionString $connection_string;# obtención de blobs (básicamente, archivos)Write-Host ("Getting blobs from container...");$fre_blobs = Get-AzStorageBlob -Container $container_name -Context $storage_account;# ruta al almacenamiento local (consulte 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';# descarga del único blob necesarioWrite-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."); }}# descompresión del paquete descargado (la biblioteca anterior se sobrescribirá a la fuerza)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 para preparar el paquete del servicio de licencias e iniciar 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 "PrepareLS.ps1 started...";# contenedor con los paquetes de ABBYY FineReader Engine y el servicio de licencias$container_name = 'fre-lib';# cadena de conexión de la cuenta de almacenamiento$connection_string = "<connection_string_to_frestorage>";Write-Host "Configuration";Write-Host ("Container name: " + $container_name);Write-Host ("Connection string: " + $storage_account);# conexión al objeto de almacenamientoWrite-Host ("Connecting to storage account...");$storage_account = New-AzStorageContext -ConnectionString $connection_string;# obtención de blobs (básicamente archivos)Write-Host ("Getting blobs from container...");$fre_blobs = Get-AzStorageBlob -Container $container_name -Context $storage_account;# ruta al almacenamiento local (consulte 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'; # descarga del paquete LSWrite-Host ("Downloading Licensing Service package from container...");foreach( $blob in $fre_blobs ) { Write-Host $blob.Name; if( $blob.Name -eq "LSPackage.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."); }}# descompresión del paquete descargadoWrite-Host ("Unzipping library package...");Expand-Archive -Path (Join-Path -Path $destination_path -ChildPath "LSPackage.zip") ` -DestinationPath (Join-Path -Path $destination_path -ChildPath "LSPackage") -Force;# creación de carpetas para las licencias$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;# inicio del servicio de licencias en modo independienteWrite-Host ("Starting Licensing Service...");$licensing_service_app = Join-Path -Path $destination_path -ChildPath "LSPackage\LicensingService.exe";Start-Process -FilePath $licesing_service_app -ArgumentList "/standalone";Write-Host ("LS package was prepared and LS was started.");
Implementación de la interfaz 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() { // Creación de la conexión a un nuevo blob en <OutputContainerName> // El resultado del procesamiento se almacenará allí BlobClient resultBlobClient = new BlobClient(Config.ConnectionString, Config.OutputContainerName, resultBlobName + fileExtension); // Sobrescritura del archivo existente resultBlobClient.DeleteIfExists(); // Posición establecida en 0 para escribir el archivo desde el principio stream.Position = 0; resultBlobClient.Upload(stream); stream.Close(); } public void Dispose() { // Cierre del flujo de memoria al liberar el objeto para poder acceder a él tras escribir los datos stream.Close(); } private string resultBlobName; private string fileExtension; private MemoryStream stream; }}
Ejemplo de procesamiento de archivos con 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 is running"); try { this.RunAsync(this.cancellationTokenSource.Token).Wait(); } finally { this.runCompleteEvent.Set(); } } public override bool OnStart() { // Establezca el número máximo de conexiones simultáneas ServicePointManager.DefaultConnectionLimit = 12; // Establezca TLS en 12 para poder conectarse a la cuenta de almacenamiento System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; // Para obtener información sobre cómo administrar los cambios de configuración, // consulte el tema de MSDN en https://go.microsoft.com/fwlink/?LinkId=166357 bool result = base.OnStart(); Trace.TraceInformation("WorkerRole1 has been started"); processor.LoadEngine(); return result; } public override void OnStop() { processor.UnloadEngine(); Trace.TraceInformation("WorkerRole1 is stopping"); this.cancellationTokenSource.Cancel(); this.runCompleteEvent.WaitOne(); base.OnStop(); Trace.TraceInformation("WorkerRole1 has stopped"); } private async Task RunAsync(CancellationToken cancellationToken) { // TODO: Reemplace lo siguiente por su propia lógica while (!cancellationToken.IsCancellationRequested) { Trace.TraceInformation("Working"); try { // Cree el contenedor y devuelva un objeto cliente de contenedor 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 Downloading blob to memory: " + blobItem.Name); MemoryStream memoryStream = new MemoryStream(); blobClient.DownloadTo(memoryStream); Trace.TraceInformation("\t Processing in FRE: " + blobItem.Name); string resultBlobName = processor.ProcessImage(memoryStream, blobItem.Name); Trace.TraceInformation("\t Result blob name in output container: " + resultBlobName); Trace.TraceInformation("\t Deleting from input container: " + blobItem.Name); blobClient.Delete(); } } catch (Exception error) { Trace.TraceInformation("error: " + 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("Cargando perfil predefinido..."); loadProfile(); } public void LoadEngine() { try { if (engineLoader == null) { // El mismo EngineLoader que en el ejemplo 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; // Agregue el archivo de imagen al documento displayMessage("Cargando imagen..."); IntPtr handle = Marshal.AllocHGlobal(inputMemoryStream.GetBuffer().Length); Marshal.Copy(inputMemoryStream.GetBuffer(), 0, handle, inputMemoryStream.GetBuffer().Length); document.AddImageFileFromMemory(handle.ToInt64(), null, null); // Reconozca el documento displayMessage("Reconociendo..."); document.Process(null); // Guarde los resultados displayMessage("Guardando resultados..."); // Guarde los resultados en PDF con el escenario '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 { // Cierre el documento document.Close(); } return resultBlobName; } public void Dispose() { UnloadEngine(); } }}
Config.cs
class Config{ // Almacenamiento local, tal como se define en ServiceDefinition.csdef private static string LocalStorageName = "LocalStorage1"; // Cadena de conexión al contenedor de blobs public static readonly string ConnectionString = "<connection_string_to_frestorage>"; // Nombres de los contenedores de entrada y salida en su almacenamiento public static readonly string InputContainerName = "fre-input"; public static readonly string OutputContainerName = "fre-output"; // Devuelve el Customer Project ID para ABBYY FineReader Engine public static String GetCustomerProjectId() { return "<Your_Customer_Project_ID>"; } // Devuelve el nombre del token de licencia public static String GetLicenseTokenName() { return "<Token_number>.ABBYY.ActivationToken"; } // Devuelve la contraseña de la licencia public static String GetLicensePassword() { return "<Your_Online_License_token_password>"; } // Devuelve la ruta de la carpeta del motor public static String GetEngineFolder() { string engineSubfolder = "fre_packages\\LibraryPackage\\Bin64"; string engineDllFolder = Path.Combine(RoleEnvironment.GetLocalResource(LocalStorageName).RootPath, engineSubfolder); return engineDllFolder; }}