139 lines
4.9 KiB
PHP
139 lines
4.9 KiB
PHP
<?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();
|
|
}
|
|
}
|