Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -3,14 +3,12 @@ import uuid
|
|
| 3 |
import requests
|
| 4 |
import shutil
|
| 5 |
import subprocess
|
| 6 |
-
import
|
| 7 |
-
import cv2 # Tambahkan import cv2
|
| 8 |
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
|
| 9 |
from fastapi.responses import HTMLResponse, FileResponse
|
| 10 |
from vidstab import VidStab
|
| 11 |
|
| 12 |
# --- FIX UNTUK ERROR HEADLESS OPENCV ---
|
| 13 |
-
# Mencegah vidstab memanggil fungsi GUI yang menyebabkan crash di server (cvDestroyAllWindows)
|
| 14 |
cv2.destroyAllWindows = lambda: None
|
| 15 |
cv2.imshow = lambda *args, **kwargs: None
|
| 16 |
cv2.waitKey = lambda *args, **kwargs: 1
|
|
@@ -31,7 +29,7 @@ async def index():
|
|
| 31 |
<head>
|
| 32 |
<meta charset="UTF-8">
|
| 33 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 34 |
-
<title>AI Video Stabilizer</title>
|
| 35 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 36 |
<style>
|
| 37 |
.loader { border-top-color: #4f46e5; animation: spinner 1.5s linear infinite; }
|
|
@@ -40,8 +38,8 @@ async def index():
|
|
| 40 |
</head>
|
| 41 |
<body class="bg-slate-50 min-h-screen flex flex-col items-center p-4 font-sans">
|
| 42 |
<div class="w-full max-w-md bg-white rounded-3xl shadow-2xl p-8 mt-10 border border-slate-100">
|
| 43 |
-
<h1 class="text-3xl font-black text-center text-indigo-600 mb-2">STABILIZER</h1>
|
| 44 |
-
<p class="text-center text-slate-400 text-sm mb-8">
|
| 45 |
|
| 46 |
<form id="uploadForm" class="space-y-6">
|
| 47 |
<div class="group relative border-2 border-dashed border-slate-200 rounded-2xl p-8 text-center hover:border-indigo-400 transition-colors">
|
|
@@ -71,7 +69,10 @@ async def index():
|
|
| 71 |
<div id="status" class="mt-8 hidden">
|
| 72 |
<div class="flex flex-col items-center justify-center space-y-4">
|
| 73 |
<div class="loader ease-linear rounded-full border-4 border-t-4 border-slate-200 h-12 w-12"></div>
|
| 74 |
-
<p class="text-indigo-600 font-bold text-sm animate-pulse text-center">
|
|
|
|
|
|
|
|
|
|
| 75 |
</div>
|
| 76 |
</div>
|
| 77 |
|
|
@@ -137,14 +138,11 @@ async def index():
|
|
| 137 |
async def stabilize_video(file: UploadFile = File(None), url: str = Form(None)):
|
| 138 |
job_id = str(uuid.uuid4())
|
| 139 |
input_path = os.path.join(UPLOAD_DIR, f"{job_id}_in.mp4")
|
| 140 |
-
|
| 141 |
-
# FIX: Ubah ekstensi raw dari .mp4 ke .avi.
|
| 142 |
-
# Karena OpenCV menggunakan MJPG secara default, format ini sangat kompatibel dengan kontainer .avi
|
| 143 |
raw_output_path = os.path.join(OUTPUT_DIR, f"{job_id}_raw.avi")
|
| 144 |
final_output_path = os.path.join(OUTPUT_DIR, f"{job_id}_final.mp4")
|
| 145 |
|
| 146 |
try:
|
| 147 |
-
# 1. Simpan Input
|
| 148 |
if file:
|
| 149 |
with open(input_path, "wb") as buffer:
|
| 150 |
shutil.copyfileobj(file.file, buffer)
|
|
@@ -158,16 +156,37 @@ async def stabilize_video(file: UploadFile = File(None), url: str = Form(None)):
|
|
| 158 |
|
| 159 |
# 2. Proses Stabilisasi (VidStab)
|
| 160 |
stabilizer = VidStab()
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
ffmpeg_cmd = [
|
| 166 |
-
'ffmpeg', '-y',
|
| 167 |
-
'-
|
| 168 |
-
'-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
final_output_path
|
| 170 |
]
|
|
|
|
|
|
|
| 171 |
subprocess.run(ffmpeg_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
| 172 |
|
| 173 |
# 4. Cleanup file sementara
|
|
|
|
| 3 |
import requests
|
| 4 |
import shutil
|
| 5 |
import subprocess
|
| 6 |
+
import cv2
|
|
|
|
| 7 |
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
|
| 8 |
from fastapi.responses import HTMLResponse, FileResponse
|
| 9 |
from vidstab import VidStab
|
| 10 |
|
| 11 |
# --- FIX UNTUK ERROR HEADLESS OPENCV ---
|
|
|
|
| 12 |
cv2.destroyAllWindows = lambda: None
|
| 13 |
cv2.imshow = lambda *args, **kwargs: None
|
| 14 |
cv2.waitKey = lambda *args, **kwargs: 1
|
|
|
|
| 29 |
<head>
|
| 30 |
<meta charset="UTF-8">
|
| 31 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 32 |
+
<title>AI Video Stabilizer Pro</title>
|
| 33 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 34 |
<style>
|
| 35 |
.loader { border-top-color: #4f46e5; animation: spinner 1.5s linear infinite; }
|
|
|
|
| 38 |
</head>
|
| 39 |
<body class="bg-slate-50 min-h-screen flex flex-col items-center p-4 font-sans">
|
| 40 |
<div class="w-full max-w-md bg-white rounded-3xl shadow-2xl p-8 mt-10 border border-slate-100">
|
| 41 |
+
<h1 class="text-3xl font-black text-center text-indigo-600 mb-2">STABILIZER <span class="text-emerald-500">PRO</span></h1>
|
| 42 |
+
<p class="text-center text-slate-400 text-sm mb-8">Anti-Shake + Audio Preserved + Border Fill</p>
|
| 43 |
|
| 44 |
<form id="uploadForm" class="space-y-6">
|
| 45 |
<div class="group relative border-2 border-dashed border-slate-200 rounded-2xl p-8 text-center hover:border-indigo-400 transition-colors">
|
|
|
|
| 69 |
<div id="status" class="mt-8 hidden">
|
| 70 |
<div class="flex flex-col items-center justify-center space-y-4">
|
| 71 |
<div class="loader ease-linear rounded-full border-4 border-t-4 border-slate-200 h-12 w-12"></div>
|
| 72 |
+
<p class="text-indigo-600 font-bold text-sm animate-pulse text-center">
|
| 73 |
+
AI sedang bekerja...<br>
|
| 74 |
+
<span class="text-[10px] text-slate-400">Menghilangkan guncangan, mengisi border & menyatukan audio</span>
|
| 75 |
+
</p>
|
| 76 |
</div>
|
| 77 |
</div>
|
| 78 |
|
|
|
|
| 138 |
async def stabilize_video(file: UploadFile = File(None), url: str = Form(None)):
|
| 139 |
job_id = str(uuid.uuid4())
|
| 140 |
input_path = os.path.join(UPLOAD_DIR, f"{job_id}_in.mp4")
|
|
|
|
|
|
|
|
|
|
| 141 |
raw_output_path = os.path.join(OUTPUT_DIR, f"{job_id}_raw.avi")
|
| 142 |
final_output_path = os.path.join(OUTPUT_DIR, f"{job_id}_final.mp4")
|
| 143 |
|
| 144 |
try:
|
| 145 |
+
# 1. Simpan Input Video
|
| 146 |
if file:
|
| 147 |
with open(input_path, "wb") as buffer:
|
| 148 |
shutil.copyfileobj(file.file, buffer)
|
|
|
|
| 156 |
|
| 157 |
# 2. Proses Stabilisasi (VidStab)
|
| 158 |
stabilizer = VidStab()
|
| 159 |
+
|
| 160 |
+
# PERBAIKAN KUALITAS:
|
| 161 |
+
# - smoothing_window=30 : Membuat gerakan kamera jauh lebih mulus (seperti pakai gimbal).
|
| 162 |
+
# - border_type='replicate' : Menghilangkan kotak/border hitam dengan cara mereplikasi pixel terluar.
|
| 163 |
+
stabilizer.stabilize(
|
| 164 |
+
input_path=input_path,
|
| 165 |
+
output_path=raw_output_path,
|
| 166 |
+
smoothing_window=30,
|
| 167 |
+
border_type='replicate',
|
| 168 |
+
playback=False
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
# 3. Konversi + GABUNGKAN AUDIO (Audio Muxing)
|
| 172 |
+
# Mengambil Video dari file .avi (hasil stabil) dan Audio dari file .mp4 (video asli)
|
| 173 |
ffmpeg_cmd = [
|
| 174 |
+
'ffmpeg', '-y',
|
| 175 |
+
'-i', raw_output_path, # Input 0: Video Stabil (Tidak bersuara)
|
| 176 |
+
'-i', input_path, # Input 1: Video Asli (Bersuara)
|
| 177 |
+
'-map', '0:v:0', # Ambil track Video dari Input 0
|
| 178 |
+
'-map', '1:a:0?', # Ambil track Audio dari Input 1 (tanda '?' = abaikan jika video asli memang bisu)
|
| 179 |
+
'-c:v', 'libx264', # Codec Video
|
| 180 |
+
'-preset', 'fast', # Kecepatan render sedikit lebih baik
|
| 181 |
+
'-crf', '23', # Kualitas visual ditingkatkan (semakin kecil = semakin jernih)
|
| 182 |
+
'-pix_fmt', 'yuv420p',
|
| 183 |
+
'-c:a', 'aac', # Codec Audio
|
| 184 |
+
'-b:a', '128k', # Bitrate Audio
|
| 185 |
+
'-movflags', '+faststart',
|
| 186 |
final_output_path
|
| 187 |
]
|
| 188 |
+
|
| 189 |
+
# Jalankan FFmpeg
|
| 190 |
subprocess.run(ffmpeg_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
| 191 |
|
| 192 |
# 4. Cleanup file sementara
|