diff --git a/app/Jobs/TutorialVideoJob.php b/app/Jobs/TutorialVideoJob.php index 0daecdae..bf4f6e4c 100644 --- a/app/Jobs/TutorialVideoJob.php +++ b/app/Jobs/TutorialVideoJob.php @@ -46,6 +46,7 @@ public function handle( if (! $tutorial) { Log::error('TutorialVideoJob: 레코드를 찾을 수 없음', ['id' => $this->tutorialVideoId]); + return; } @@ -67,6 +68,7 @@ public function handle( if (empty($screenshots)) { $tutorial->markFailed('업로드된 스크린샷이 없습니다'); + return; } @@ -74,6 +76,7 @@ public function handle( if (empty($analysisData)) { $tutorial->markFailed('스크린샷 분석에 실패했습니다'); + return; } @@ -126,6 +129,7 @@ public function handle( if (! $imagePath || ! file_exists($imagePath)) { Log::warning("TutorialVideoJob: 스크린샷 없음 - index {$i}"); + continue; } @@ -176,6 +180,7 @@ public function handle( if (count($slidePaths) <= 2) { // 인트로+아웃트로만 있으면 실패 $tutorial->markFailed('슬라이드 생성에 실패했습니다'); + return; } @@ -257,6 +262,7 @@ public function handle( if (! $result || ! file_exists($finalOutputPath)) { $tutorial->markFailed('영상 합성에 실패했습니다'); + return; } @@ -267,13 +273,26 @@ public function handle( $gcsPath = null; if ($gcs->isAvailable()) { - $objectName = "tutorials/{$tutorial->tenant_id}/{$tutorial->id}/tutorial_" . date('Ymd_His') . '.mp4'; + $objectName = "tutorials/{$tutorial->tenant_id}/{$tutorial->id}/tutorial_".date('Ymd_His').'.mp4'; $gcsPath = $gcs->upload($finalOutputPath, $objectName); } $tutorial->updateProgress(TutorialVideo::STATUS_ASSEMBLING, 95, '업로드 완료'); // === Step 7: 완료 (100%) === + // analysis_data에 영상 저장 경로 메타데이터 추가 + $analysisWithMeta = $tutorial->analysis_data ?? $analysisData; + if (is_array($analysisWithMeta)) { + $analysisWithMeta['_output'] = [ + 'completed_at' => now()->toIso8601String(), + 'output_path' => $finalOutputPath, + 'gcs_path' => $gcsPath, + 'cost_usd' => round($totalCost, 4), + 'total_slides' => count($slidePaths), + 'total_duration' => $totalDuration, + ]; + } + $tutorial->update([ 'status' => TutorialVideo::STATUS_COMPLETED, 'progress' => 100, @@ -281,6 +300,7 @@ public function handle( 'output_path' => $finalOutputPath, 'gcs_path' => $gcsPath, 'cost_usd' => $totalCost, + 'analysis_data' => $analysisWithMeta, ]); Log::info('TutorialVideoJob: 완료', [ diff --git a/app/Services/Video/ScreenAnalysisService.php b/app/Services/Video/ScreenAnalysisService.php index 459c3a14..efd962d3 100644 --- a/app/Services/Video/ScreenAnalysisService.php +++ b/app/Services/Video/ScreenAnalysisService.php @@ -624,9 +624,28 @@ private function runCoordinateVerification(string $imagePath, array $parsed): ar // Gemini 2-pass 검증 호출 $verifiedSteps = $this->verifyCoordinates($verificationImagePath, $stepsWithElement); - // 보정 적용 + // 보정 적용 + 검증 메타데이터 저장 $parsed = $this->applyVerifiedCoordinates($parsed, $verifiedSteps); + // 스크린 단위 검증 통계 저장 + $accurateCount = 0; + $correctedCount = 0; + if ($verifiedSteps) { + foreach ($verifiedSteps as $v) { + if ($v['accurate'] ?? true) { + $accurateCount++; + } else { + $correctedCount++; + } + } + } + $parsed['_verification'] = [ + 'verified_at' => now()->toIso8601String(), + 'total_steps' => count($stepsWithElement), + 'accurate' => $accurateCount, + 'corrected' => $correctedCount, + ]; + // 검증 이미지 정리 if (file_exists($verificationImagePath)) { @unlink($verificationImagePath); @@ -916,7 +935,9 @@ private function applyVerifiedCoordinates(array $parsed, ?array $verifiedSteps): continue; } - if (! ($verification['accurate'] ?? true)) { + $isAccurate = $verification['accurate'] ?? true; + + if (! $isAccurate) { $oldX = $step['focused_element']['x']; $oldY = $step['focused_element']['y']; $oldW = $step['focused_element']['w']; @@ -927,11 +948,21 @@ private function applyVerifiedCoordinates(array $parsed, ?array $verifiedSteps): $step['focused_element']['w'] = $verification['corrected_w'] ?? $oldW; $step['focused_element']['h'] = $verification['corrected_h'] ?? $oldH; + // 보정 전 좌표를 메타데이터로 저장 + $step['focused_element']['_verification'] = [ + 'accurate' => false, + 'original' => ['x' => $oldX, 'y' => $oldY, 'w' => $oldW, 'h' => $oldH], + ]; + Log::info("ScreenAnalysis: 좌표 보정 Step {$stepNum}", [ 'label' => $step['focused_element']['label'] ?? '?', 'before' => "x={$oldX}, y={$oldY}, w={$oldW}, h={$oldH}", 'after' => "x={$step['focused_element']['x']}, y={$step['focused_element']['y']}, w={$step['focused_element']['w']}, h={$step['focused_element']['h']}", ]); + } else { + $step['focused_element']['_verification'] = [ + 'accurate' => true, + ]; } } unset($step);