Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -139,12 +139,11 @@ def apply_watermark(input_video, watermark_img, output_video):
|
|
| 139 |
f"if(lt(mod(t,15),12), (H-h-10) - {speed}*mod(t,3), "
|
| 140 |
"(H-h)/2 + 20*cos(t)))))"
|
| 141 |
)
|
| 142 |
-
# استفاده از re-encode برای اطمینان از سینک بودن واترمارک با فریمها
|
| 143 |
subprocess.run([
|
| 144 |
"ffmpeg", "-y", "-i", input_video, "-i", watermark_img,
|
| 145 |
"-filter_complex", f"[0:v][1:v]overlay=x='{expr_x}':y='{expr_y}'",
|
| 146 |
"-c:a", "copy",
|
| 147 |
-
"-c:v", "libx264", "-preset", "ultrafast", "-crf", "24",
|
| 148 |
output_video
|
| 149 |
], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
| 150 |
return True
|
|
@@ -155,12 +154,6 @@ def apply_watermark(input_video, watermark_img, output_video):
|
|
| 155 |
# --- توابع پردازش هوشمند صدا (برش دقیق) ---
|
| 156 |
|
| 157 |
def smart_split_audio(file_path, job_id):
|
| 158 |
-
"""
|
| 159 |
-
الگوریتم برش دقیق:
|
| 160 |
-
1. اولویت: بازه ۱۴ تا ۱۹ ثانیه.
|
| 161 |
-
2. پشتیبان: بازه ۹ تا ۱۴ ثانیه (برای جلوگیری از برش وسط کلمه).
|
| 162 |
-
3. آخر خط: برش سر ۱۹ ثانیه.
|
| 163 |
-
"""
|
| 164 |
try:
|
| 165 |
audio = AudioSegment.from_file(file_path)
|
| 166 |
total_len = len(audio)
|
|
@@ -179,8 +172,6 @@ def smart_split_audio(file_path, job_id):
|
|
| 179 |
cut_point = 0
|
| 180 |
found_cut = False
|
| 181 |
|
| 182 |
-
# جستجو در بازه ۱۴ تا ۱۹ (ایدهآل)
|
| 183 |
-
# از SILENCE_MIN_LEN 250 استفاده میکنیم تا مکث های کوتاه تر هم پیدا شن
|
| 184 |
search_start = start + 14000
|
| 185 |
search_end = min(start + MAX_CUT_TIME, total_len)
|
| 186 |
|
|
@@ -189,13 +180,11 @@ def smart_split_audio(file_path, job_id):
|
|
| 189 |
silences = detect_silence(chunk_search, min_silence_len=SILENCE_MIN_LEN, silence_thresh=SILENCE_THRESH)
|
| 190 |
|
| 191 |
if silences:
|
| 192 |
-
# وسط سکوت برش بزن
|
| 193 |
s = silences[0]
|
| 194 |
mid = s[0] + (s[1] - s[0]) // 2
|
| 195 |
cut_point = search_start + mid
|
| 196 |
found_cut = True
|
| 197 |
|
| 198 |
-
# اگر در ۱۴-۱۹ پیدا نشد، در بازه ۹ تا ۱۴ بگرد (برای حفظ کیفیت بهتر از طولانی بودن است)
|
| 199 |
if not found_cut:
|
| 200 |
search_start_safe = start + 9000
|
| 201 |
search_end_safe = start + 14000
|
|
@@ -209,7 +198,6 @@ def smart_split_audio(file_path, job_id):
|
|
| 209 |
cut_point = search_start_safe + mid
|
| 210 |
found_cut = True
|
| 211 |
|
| 212 |
-
# اگر هیچ جا سکوت نبود (اجباری)
|
| 213 |
if not found_cut:
|
| 214 |
cut_point = start + MAX_CUT_TIME
|
| 215 |
|
|
@@ -240,9 +228,6 @@ def convert_audio_to_wav(input_path, output_path):
|
|
| 240 |
except: return False
|
| 241 |
|
| 242 |
def merge_videos_smart(video_map, total_parts, output_path):
|
| 243 |
-
"""
|
| 244 |
-
ادغام ویدیوها با Re-encode (بازسازی) برای جلوگیری از حذف فریم و کسر زمان
|
| 245 |
-
"""
|
| 246 |
try:
|
| 247 |
videos = []
|
| 248 |
for i in range(total_parts):
|
|
@@ -260,17 +245,13 @@ def merge_videos_smart(video_map, total_parts, output_path):
|
|
| 260 |
for v in videos:
|
| 261 |
f.write(f"file '{os.path.abspath(v)}'\n")
|
| 262 |
|
| 263 |
-
# تغییر حیاتی: استفاده از concat filter بجای concat demuxer
|
| 264 |
-
# روش قبلی (-c copy) باعث حذف فریمها در نقاط اتصال میشد.
|
| 265 |
-
# این روش ویدیو را دوباره رندر میکند تا فریمها دقیقاً پشت هم قرار بگیرند.
|
| 266 |
-
# از ultrafast استفاده میکنیم تا سرعت بالا بماند.
|
| 267 |
subprocess.run([
|
| 268 |
"ffmpeg", "-y",
|
| 269 |
"-f", "concat",
|
| 270 |
"-safe", "0",
|
| 271 |
"-i", "list.txt",
|
| 272 |
-
"-c:v", "libx264", "-preset", "ultrafast", "-crf", "24",
|
| 273 |
-
"-c:a", "aac",
|
| 274 |
output_path
|
| 275 |
], check=True)
|
| 276 |
return True
|
|
@@ -278,12 +259,6 @@ def merge_videos_smart(video_map, total_parts, output_path):
|
|
| 278 |
logger.error(f"Merge Error: {e}")
|
| 279 |
return False
|
| 280 |
|
| 281 |
-
def add_audio_to_video(video_path, audio_path, output_path):
|
| 282 |
-
try:
|
| 283 |
-
subprocess.run(["ffmpeg", "-y", "-i", video_path, "-i", audio_path, "-c:v", "copy", "-c:a", "aac", "-map", "0:v:0", "-map", "1:a:0", "-shortest", output_path], check=True)
|
| 284 |
-
return True
|
| 285 |
-
except: return False
|
| 286 |
-
|
| 287 |
# --- توزیع کار ---
|
| 288 |
|
| 289 |
async def dispatch_all_parallel(job_id, img_path, audio_files, resolution):
|
|
@@ -384,9 +359,25 @@ async def create_job(background_tasks: BackgroundTasks, image: UploadFile = File
|
|
| 384 |
if not convert_audio_to_wav(temp_audio, final_audio):
|
| 385 |
return JSONResponse(status_code=400, content={"error": "Audio conversion failed"})
|
| 386 |
|
|
|
|
| 387 |
audio_parts = smart_split_audio(final_audio, job_id)
|
| 388 |
-
total_parts = len(audio_parts)
|
| 389 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
parts_status_init = {str(i): "PENDING" for i in range(total_parts)}
|
| 391 |
|
| 392 |
db = SessionLocal()
|
|
@@ -395,7 +386,7 @@ async def create_job(background_tasks: BackgroundTasks, image: UploadFile = File
|
|
| 395 |
original_image_path=img_path, original_audio_path=final_audio,
|
| 396 |
total_parts=total_parts, parts_status=parts_status_init,
|
| 397 |
audio_parts=audio_parts, video_parts={},
|
| 398 |
-
message=
|
| 399 |
last_activity=datetime.utcnow()
|
| 400 |
)
|
| 401 |
db.add(new_job)
|
|
|
|
| 139 |
f"if(lt(mod(t,15),12), (H-h-10) - {speed}*mod(t,3), "
|
| 140 |
"(H-h)/2 + 20*cos(t)))))"
|
| 141 |
)
|
|
|
|
| 142 |
subprocess.run([
|
| 143 |
"ffmpeg", "-y", "-i", input_video, "-i", watermark_img,
|
| 144 |
"-filter_complex", f"[0:v][1:v]overlay=x='{expr_x}':y='{expr_y}'",
|
| 145 |
"-c:a", "copy",
|
| 146 |
+
"-c:v", "libx264", "-preset", "ultrafast", "-crf", "24",
|
| 147 |
output_video
|
| 148 |
], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
| 149 |
return True
|
|
|
|
| 154 |
# --- توابع پردازش هوشمند صدا (برش دقیق) ---
|
| 155 |
|
| 156 |
def smart_split_audio(file_path, job_id):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
try:
|
| 158 |
audio = AudioSegment.from_file(file_path)
|
| 159 |
total_len = len(audio)
|
|
|
|
| 172 |
cut_point = 0
|
| 173 |
found_cut = False
|
| 174 |
|
|
|
|
|
|
|
| 175 |
search_start = start + 14000
|
| 176 |
search_end = min(start + MAX_CUT_TIME, total_len)
|
| 177 |
|
|
|
|
| 180 |
silences = detect_silence(chunk_search, min_silence_len=SILENCE_MIN_LEN, silence_thresh=SILENCE_THRESH)
|
| 181 |
|
| 182 |
if silences:
|
|
|
|
| 183 |
s = silences[0]
|
| 184 |
mid = s[0] + (s[1] - s[0]) // 2
|
| 185 |
cut_point = search_start + mid
|
| 186 |
found_cut = True
|
| 187 |
|
|
|
|
| 188 |
if not found_cut:
|
| 189 |
search_start_safe = start + 9000
|
| 190 |
search_end_safe = start + 14000
|
|
|
|
| 198 |
cut_point = search_start_safe + mid
|
| 199 |
found_cut = True
|
| 200 |
|
|
|
|
| 201 |
if not found_cut:
|
| 202 |
cut_point = start + MAX_CUT_TIME
|
| 203 |
|
|
|
|
| 228 |
except: return False
|
| 229 |
|
| 230 |
def merge_videos_smart(video_map, total_parts, output_path):
|
|
|
|
|
|
|
|
|
|
| 231 |
try:
|
| 232 |
videos = []
|
| 233 |
for i in range(total_parts):
|
|
|
|
| 245 |
for v in videos:
|
| 246 |
f.write(f"file '{os.path.abspath(v)}'\n")
|
| 247 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
subprocess.run([
|
| 249 |
"ffmpeg", "-y",
|
| 250 |
"-f", "concat",
|
| 251 |
"-safe", "0",
|
| 252 |
"-i", "list.txt",
|
| 253 |
+
"-c:v", "libx264", "-preset", "ultrafast", "-crf", "24",
|
| 254 |
+
"-c:a", "aac",
|
| 255 |
output_path
|
| 256 |
], check=True)
|
| 257 |
return True
|
|
|
|
| 259 |
logger.error(f"Merge Error: {e}")
|
| 260 |
return False
|
| 261 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
# --- توزیع کار ---
|
| 263 |
|
| 264 |
async def dispatch_all_parallel(job_id, img_path, audio_files, resolution):
|
|
|
|
| 359 |
if not convert_audio_to_wav(temp_audio, final_audio):
|
| 360 |
return JSONResponse(status_code=400, content={"error": "Audio conversion failed"})
|
| 361 |
|
| 362 |
+
# برش صدا
|
| 363 |
audio_parts = smart_split_audio(final_audio, job_id)
|
|
|
|
| 364 |
|
| 365 |
+
# --- منطق جدید برای کاربران رایگان ---
|
| 366 |
+
# اگر کاربر رایگان است و تعداد پارتها بیشتر از ۱ است، فقط پارت اول را نگه دار
|
| 367 |
+
if not is_premium and len(audio_parts) > 1:
|
| 368 |
+
# حذف فایلهای اضافی از حافظه
|
| 369 |
+
for part in audio_parts[1:]:
|
| 370 |
+
try:
|
| 371 |
+
os.remove(part)
|
| 372 |
+
except:
|
| 373 |
+
pass
|
| 374 |
+
# فقط پارت اول (که زیر 19 ثانیه است) را نگه دار
|
| 375 |
+
audio_parts = [audio_parts[0]]
|
| 376 |
+
initial_msg = "شروع پردازش (محدود به ۱۹ ثانیه نسخه رایگان)..."
|
| 377 |
+
else:
|
| 378 |
+
initial_msg = f"شروع پردازش همزمان {len(audio_parts)} قسمت..."
|
| 379 |
+
|
| 380 |
+
total_parts = len(audio_parts)
|
| 381 |
parts_status_init = {str(i): "PENDING" for i in range(total_parts)}
|
| 382 |
|
| 383 |
db = SessionLocal()
|
|
|
|
| 386 |
original_image_path=img_path, original_audio_path=final_audio,
|
| 387 |
total_parts=total_parts, parts_status=parts_status_init,
|
| 388 |
audio_parts=audio_parts, video_parts={},
|
| 389 |
+
message=initial_msg,
|
| 390 |
last_activity=datetime.utcnow()
|
| 391 |
)
|
| 392 |
db.add(new_job)
|