diff --git a/app/Http/Controllers/Video/Veo3Controller.php b/app/Http/Controllers/Video/Veo3Controller.php index ec398278..93ef3801 100644 --- a/app/Http/Controllers/Video/Veo3Controller.php +++ b/app/Http/Controllers/Video/Veo3Controller.php @@ -5,8 +5,10 @@ use App\Http\Controllers\Controller; use App\Jobs\VideoGenerationJob; use App\Models\VideoGeneration; +use App\Services\GoogleCloudStorageService; use App\Services\Video\GeminiScriptService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\View\View; @@ -15,7 +17,8 @@ class Veo3Controller extends Controller { public function __construct( - private readonly GeminiScriptService $geminiService + private readonly GeminiScriptService $geminiService, + private readonly GoogleCloudStorageService $gcsService ) {} /** @@ -164,7 +167,7 @@ public function status(int $id): JsonResponse /** * 완성 영상 다운로드 */ - public function download(int $id): BinaryFileResponse|JsonResponse + public function download(int $id): BinaryFileResponse|RedirectResponse|JsonResponse { $video = VideoGeneration::findOrFail($id); @@ -172,6 +175,15 @@ public function download(int $id): BinaryFileResponse|JsonResponse return response()->json(['message' => '아직 영상이 완성되지 않았습니다.'], 404); } + // GCS 서명URL로 리다이렉트 + if ($video->gcs_path && $this->gcsService->isAvailable()) { + $signedUrl = $this->gcsService->getSignedUrl($video->gcs_path, 30); + if ($signedUrl) { + return redirect()->away($signedUrl); + } + } + + // 로컬 파일 폴백 if (! file_exists($video->output_path)) { return response()->json(['message' => '영상 파일을 찾을 수 없습니다.'], 404); } @@ -187,10 +199,19 @@ public function download(int $id): BinaryFileResponse|JsonResponse /** * 영상 미리보기 (스트리밍) */ - public function preview(int $id): Response|JsonResponse + public function preview(int $id): Response|RedirectResponse|JsonResponse { $video = VideoGeneration::findOrFail($id); + // GCS 서명URL로 리다이렉트 + if ($video->gcs_path && $this->gcsService->isAvailable()) { + $signedUrl = $this->gcsService->getSignedUrl($video->gcs_path, 60); + if ($signedUrl) { + return redirect()->away($signedUrl); + } + } + + // 로컬 파일 폴백 if (! $video->output_path || ! file_exists($video->output_path)) { return response()->json(['message' => '영상 파일을 찾을 수 없습니다.'], 404); } @@ -237,6 +258,11 @@ public function destroy(Request $request): JsonResponse $deleted = 0; foreach ($videos as $video) { + // GCS 파일 삭제 + if ($video->gcs_path && $this->gcsService->isAvailable()) { + $this->gcsService->delete($video->gcs_path); + } + // 로컬 작업 디렉토리 삭제 (클립, 나레이션, 최종 영상 등) $workDir = storage_path("app/video_gen/{$video->id}"); if (is_dir($workDir)) { diff --git a/app/Jobs/VideoGenerationJob.php b/app/Jobs/VideoGenerationJob.php index ee303dd9..c89f1b30 100644 --- a/app/Jobs/VideoGenerationJob.php +++ b/app/Jobs/VideoGenerationJob.php @@ -8,6 +8,7 @@ use App\Services\Video\TtsService; use App\Services\Video\VeoVideoService; use App\Services\Video\VideoAssemblyService; +use App\Services\GoogleCloudStorageService; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -41,7 +42,8 @@ public function handle( VeoVideoService $veo, TtsService $tts, BgmService $bgm, - VideoAssemblyService $assembly + VideoAssemblyService $assembly, + GoogleCloudStorageService $gcs ): void { $video = VideoGeneration::withoutGlobalScopes()->find($this->videoGenerationId); @@ -264,6 +266,28 @@ public function handle( return; } + // === GCS 업로드 === + $gcsPath = null; + + if ($gcs->isAvailable()) { + $video->updateProgress(VideoGeneration::STATUS_ASSEMBLING, 95, 'GCS 업로드 중...'); + + $objectName = "video_gen/{$video->id}/final_{$video->id}.mp4"; + $gcsUri = $gcs->upload($finalPath, $objectName); + + if ($gcsUri) { + $gcsPath = $objectName; + Log::info('VideoGenerationJob: GCS 업로드 성공', [ + 'id' => $video->id, + 'gcs_path' => $objectName, + ]); + } else { + Log::warning('VideoGenerationJob: GCS 업로드 실패, 로컬 파일로 계속', [ + 'id' => $video->id, + ]); + } + } + // === 완료 === $stepMsg = empty($skippedScenes) ? '완료' @@ -274,6 +298,7 @@ public function handle( 'progress' => 100, 'current_step' => $stepMsg, 'output_path' => $finalPath, + 'gcs_path' => $gcsPath, 'cost_usd' => $totalCost, ]); @@ -283,6 +308,7 @@ public function handle( Log::info('VideoGenerationJob: 영상 생성 완료', [ 'id' => $video->id, 'output' => $finalPath, + 'gcs_path' => $gcsPath, 'cost' => $totalCost, ]); } catch (\Exception $e) { diff --git a/app/Models/VideoGeneration.php b/app/Models/VideoGeneration.php index e6d2239a..4426178e 100644 --- a/app/Models/VideoGeneration.php +++ b/app/Models/VideoGeneration.php @@ -18,6 +18,7 @@ class VideoGeneration extends Model 'current_step', 'clips_data', 'output_path', + 'gcs_path', 'cost_usd', 'error_message', ];