Use local faster-whisper only for transcription.

Drop cloud and Ollama engine choices so audio stays on-machine via Docker Whisper, and tighten orphan detection plus UI around a single local flow.
This commit is contained in:
ben
2026-08-12 15:34:16 +02:00
parent bc6cb5efa4
commit 21b17c7657
10 changed files with 117 additions and 295 deletions
+31 -13
View File
@@ -78,10 +78,8 @@ class Recording extends Model
{
return Attribute::get(function (): ?string {
return match ($this->transcription_driver) {
'cloud' => 'Cloud (OpenAI Whisper)',
'local' => 'Local (faster-whisper)',
'ollama' => 'Ollama host',
default => $this->transcription_driver,
default => $this->transcription_driver ?: 'Local (faster-whisper)',
};
});
}
@@ -160,7 +158,7 @@ class Recording extends Model
try {
$job = unserialize($command);
} catch (Throwable) {
return (bool) preg_match('/id";i:'.$this->id.';/', $payload);
return $this->payloadMentionsRecording($payload);
}
return $job instanceof TranscribeRecording
@@ -168,6 +166,15 @@ class Recording extends Model
});
}
/**
* Fallback payload match when unserialize is unavailable.
*/
private function payloadMentionsRecording(string $payload): bool
{
return (bool) preg_match('/id";i:'.$this->id.';/', $payload)
|| str_contains($payload, 'id";s:'.strlen((string) $this->id).':"'.$this->id.'"');
}
/**
* Processing/pending with no worker job left (crashed worker, bad retry_after, etc.).
*/
@@ -183,12 +190,27 @@ class Recording extends Model
$reference = $this->transcription_started_at ?? $this->updated_at;
// Allow a short window after dispatch before the row appears / worker claims it.
if ($reference !== null && $reference->gt(now()->subSeconds(15))) {
if ($reference === null) {
return true;
}
// Only treat as orphaned after the job could not possibly still be running.
// (A short grace caused false failures while Whisper was still working.)
$orphanAfterSeconds = max(120, (int) config('ai.transcription_timeout', 600) + 60);
return $reference->lte(now()->subSeconds($orphanAfterSeconds));
}
/**
* Whether a payload/job belongs to this recording's transcription run start time.
*/
public function matchesTranscriptionRun(?string $runStartedAt): bool
{
if ($runStartedAt === null || $this->transcription_started_at === null) {
return false;
}
return true;
return $this->transcription_started_at->getTimestamp() === Carbon::parse($runStartedAt)->getTimestamp();
}
/**
@@ -202,11 +224,7 @@ class Recording extends Model
return false;
}
if ($runStartedAt === null || $this->transcription_started_at === null) {
return false;
}
return $this->transcription_started_at->getTimestamp() === Carbon::parse($runStartedAt)->getTimestamp();
return $this->matchesTranscriptionRun($runStartedAt);
}
/**
@@ -360,7 +378,7 @@ class Recording extends Model
public function transcriptionStatusPayload(): array
{
$startedAt = $this->transcription_started_at;
$elapsed = $startedAt ? $startedAt->diffInSeconds(now()) : null;
$elapsed = $startedAt ? (int) round($startedAt->diffInSeconds(now())) : null;
return [
'id' => $this->id,