feat:영상 파일 GCS 저장 및 삭제 연동

- VideoGenerationJob: 최종 합성 후 GCS 업로드, gcs_path 저장
- Veo3Controller: download/preview GCS 서명URL 사용, destroy GCS 파일 삭제
- VideoGeneration 모델: gcs_path fillable 추가
- GCS 불가 시 로컬 파일로 폴백

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
김보곤
2026-02-15 11:11:18 +09:00
parent 5e4a9c9291
commit 3f4cd88c20
3 changed files with 57 additions and 4 deletions

View File

@@ -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)) {