project; $project->update(['transcription_status' => 'processing']); $this->auditLog('transcription_started'); $tmpPath = null; try { $path = $project->stored_audio_path; abort_unless($storage->exists($path), 500, 'Fail audio tidak dijumpai.'); // Stream storage → temp file to avoid loading large files into memory $tmpPath = tempnam(sys_get_temp_dir(), 'whisper_'); $src = $storage->readStream($path); $dst = fopen($tmpPath, 'wb'); stream_copy_to_stream($src, $dst); fclose($src); fclose($dst); $workerUrl = rtrim(config('speech2text.transcription.worker_url'), '/'); $response = Http::timeout(540) ->attach( 'audio', fopen($tmpPath, 'rb'), basename($path), ['Content-Type' => $project->mime_type] ) ->post("{$workerUrl}/transcribe"); if (! $response->successful()) { throw new \RuntimeException("Transcription worker mengembalikan HTTP {$response->status()}"); } $result = $response->json(); if (! ($result['success'] ?? false)) { throw new \RuntimeException($result['error'] ?? 'Transcription gagal tanpa mesej ralat.'); } $project->update([ 'transcription_status' => 'completed', 'transcript_text' => $result['transcript'] ?? '', 'transcript_confidence' => $result['confidence'] ?? null, 'duration_seconds' => $result['duration_seconds'] ?? null, 'error_message' => null, 'processed_at' => now(), ]); $this->auditLog('transcription_completed', [ 'duration_seconds' => $result['duration_seconds'] ?? null, 'engine' => config('speech2text.transcription.engine'), ]); // Optional Ollama post-processing if (config('speech2text.ollama.enabled') && $project->transcript_text) { OllamaPostProcessJob::dispatch($project); } } catch (\Throwable $e) { Log::error("TranscribeAudioJob gagal untuk project #{$project->id}: {$e->getMessage()}"); $project->update([ 'transcription_status' => 'failed', 'error_message' => $e->getMessage(), ]); $this->auditLog('transcription_failed', ['error' => $e->getMessage()]); $this->fail($e); } finally { if ($tmpPath && file_exists($tmpPath)) { @unlink($tmpPath); } } } private function auditLog(string $action, array $extra = []): void { AuditLog::create([ 'actor_user_id' => null, 'actor_role' => 'system', 'action' => $action, 'project_id' => $this->project->id, 'new_values' => array_merge(['project_uuid' => $this->project->uuid], $extra), 'created_at' => now(), ]); } }