netgescon-day0/app/Services/Documenti/ImageTextExtractionService.php

139 lines
4.9 KiB
PHP
Executable File

<?php
namespace App\Services\Documenti;
use App\Models\Documento;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;
class ImageTextExtractionService
{
/**
* @return array{status: 'ok'|'skipped'|'error', message?: string, chars?: int, engine?: string}
*/
public function extractAndStore(Documento $documento, bool $force = false): array
{
$relativePath = $this->resolveImagePath($documento);
if ($relativePath === null) {
return ['status' => 'skipped', 'message' => 'Immagine non disponibile'];
}
if (! Storage::disk('local')->exists($relativePath)) {
return ['status' => 'error', 'message' => 'File immagine non trovato su storage'];
}
if (! $force) {
$existing = null;
if (Schema::hasColumn('documenti', 'contenuto_ocr')) {
$existing = $documento->contenuto_ocr;
} elseif (Schema::hasColumn('documenti', 'testo_estratto_ocr')) {
$existing = $documento->testo_estratto_ocr;
}
if (is_string($existing) && trim($existing) !== '') {
return ['status' => 'skipped', 'message' => 'Testo già presente'];
}
}
$tesseract = $this->findBinary('tesseract');
if ($tesseract === null) {
$this->updateOcrMetadata($documento, ['status' => 'pending_image_ocr', 'engine' => null, 'message' => 'tesseract non disponibile']);
return ['status' => 'skipped', 'message' => 'Tesseract non disponibile'];
}
$absolutePath = Storage::disk('local')->path($relativePath);
$language = (string) config('services.tesseract.lang', 'ita+eng');
$command = escapeshellcmd($tesseract)
. ' ' . escapeshellarg($absolutePath)
. ' stdout -l ' . escapeshellarg($language)
. ' 2>/dev/null';
$output = shell_exec($command);
$text = $this->normalizeText(is_string($output) ? $output : '');
if ($text === '') {
$this->updateOcrMetadata($documento, ['status' => 'empty', 'engine' => 'tesseract', 'language' => $language]);
return ['status' => 'error', 'message' => 'OCR immagine vuota'];
}
$update = [];
if (Schema::hasColumn('documenti', 'contenuto_ocr')) {
$update['contenuto_ocr'] = $text;
}
if (Schema::hasColumn('documenti', 'testo_estratto_ocr')) {
$update['testo_estratto_ocr'] = mb_substr($text, 0, 20000);
}
if (Schema::hasColumn('documenti', 'metadati_ocr')) {
$update['metadati_ocr'] = array_merge((array) ($documento->metadati_ocr ?? []), [
'status' => 'ok',
'engine' => 'tesseract',
'language' => $language,
'extracted_at' => now()->toIso8601String(),
]);
}
if ($update === []) {
return ['status' => 'error', 'message' => 'Schema documenti senza colonne OCR/testo'];
}
$documento->forceFill($update)->save();
return ['status' => 'ok', 'chars' => mb_strlen($text), 'engine' => 'tesseract'];
}
private function resolveImagePath(Documento $documento): ?string
{
$candidate = null;
if (isset($documento->percorso_file) && is_string($documento->percorso_file) && $documento->percorso_file !== '') {
$candidate = $documento->percorso_file;
} elseif (isset($documento->path_file) && is_string($documento->path_file) && $documento->path_file !== '') {
$candidate = $documento->path_file;
}
if (! is_string($candidate) || trim($candidate) === '') {
return null;
}
$candidate = trim($candidate);
$lower = strtolower($candidate);
foreach (['.png', '.jpg', '.jpeg', '.webp', '.tif', '.tiff', '.bmp'] as $extension) {
if (str_ends_with($lower, $extension)) {
return $candidate;
}
}
return null;
}
private function findBinary(string $name): ?string
{
$path = shell_exec('command -v ' . escapeshellarg($name) . ' 2>/dev/null');
$path = is_string($path) ? trim($path) : '';
return $path !== '' ? $path : null;
}
private function normalizeText(string $text): string
{
$text = str_replace("\r\n", "\n", $text);
$text = str_replace("\r", "\n", $text);
$text = str_replace("\0", '', $text);
if (! mb_check_encoding($text, 'UTF-8')) {
$text = mb_convert_encoding($text, 'UTF-8');
}
return trim($text);
}
private function updateOcrMetadata(Documento $documento, array $metadata): void
{
if (! Schema::hasColumn('documenti', 'metadati_ocr')) {
return;
}
$documento->forceFill([
'metadati_ocr' => array_merge((array) ($documento->metadati_ocr ?? []), $metadata),
])->save();
}
}