Update app.py
Browse files
app.py
CHANGED
|
@@ -412,7 +412,7 @@ def process_image_job(job):
|
|
| 412 |
'duration': f"{(datetime.now() - start).total_seconds():.2f}s"
|
| 413 |
}
|
| 414 |
|
| 415 |
-
@spaces.GPU
|
| 416 |
def process_video_job(job):
|
| 417 |
start = datetime.now()
|
| 418 |
cap = cv2.VideoCapture(job['video'])
|
|
@@ -420,16 +420,21 @@ def process_video_job(job):
|
|
| 420 |
w, h = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 421 |
|
| 422 |
frames = []
|
| 423 |
-
limit = job.get('frame_limit',
|
|
|
|
|
|
|
|
|
|
| 424 |
count = 0
|
| 425 |
while cap.isOpened():
|
| 426 |
ret, frame = cap.read()
|
| 427 |
-
if not ret or
|
| 428 |
break
|
| 429 |
frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
|
| 430 |
count += 1
|
| 431 |
cap.release()
|
| 432 |
|
|
|
|
|
|
|
| 433 |
session = VID_PROCESSOR.init_video_session(video=frames, inference_device=device, dtype=torch.bfloat16)
|
| 434 |
session = VID_PROCESSOR.add_text_prompt(inference_session=session, text=job['prompt'])
|
| 435 |
|
|
@@ -444,6 +449,8 @@ def process_video_job(job):
|
|
| 444 |
]
|
| 445 |
|
| 446 |
total = len(frames)
|
|
|
|
|
|
|
| 447 |
for idx, out in enumerate(VID_MODEL.propagate_in_video_iterator(inference_session=session, max_frame_num_to_track=total)):
|
| 448 |
proc = VID_PROCESSOR.postprocess_outputs(session, out)
|
| 449 |
orig = Image.fromarray(frames[out.frame_idx])
|
|
@@ -480,11 +487,19 @@ def process_video_job(job):
|
|
| 480 |
writers[1].write(np.zeros((h, w, 3), dtype=np.uint8))
|
| 481 |
writers[2].write(orig_bgr)
|
| 482 |
|
| 483 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 484 |
|
| 485 |
for w in writers:
|
| 486 |
w.release()
|
| 487 |
|
|
|
|
|
|
|
| 488 |
return {
|
| 489 |
'output_path': out_path,
|
| 490 |
'mask_video_path': mask_path,
|
|
@@ -530,49 +545,65 @@ def process_click_job(job):
|
|
| 530 |
# ============ BACKGROUND WORKER ============
|
| 531 |
def background_worker():
|
| 532 |
while True:
|
| 533 |
-
job = processing_queue.get()
|
| 534 |
-
if job is None:
|
| 535 |
-
break
|
| 536 |
-
|
| 537 |
-
processing_results[job['id']] = {'status': 'processing', 'progress': 0}
|
| 538 |
-
|
| 539 |
try:
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
result = process_video_job(job)
|
| 544 |
-
elif job['type'] == 'click':
|
| 545 |
-
result = process_click_job(job)
|
| 546 |
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
'result': result,
|
| 550 |
-
'progress': 100
|
| 551 |
-
}
|
| 552 |
|
| 553 |
-
|
| 554 |
-
'id': job['id'],
|
| 555 |
-
'type': job['type'],
|
| 556 |
-
'prompt': job.get('prompt', 'N/A'),
|
| 557 |
-
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
| 558 |
-
'status': 'completed',
|
| 559 |
-
**result
|
| 560 |
-
})
|
| 561 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 562 |
except Exception as e:
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
'progress': 0
|
| 567 |
-
}
|
| 568 |
-
save_history({
|
| 569 |
-
'id': job['id'],
|
| 570 |
-
'type': job['type'],
|
| 571 |
-
'prompt': job.get('prompt', 'N/A'),
|
| 572 |
-
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
| 573 |
-
'status': 'error',
|
| 574 |
-
'error': str(e)
|
| 575 |
-
})
|
| 576 |
|
| 577 |
threading.Thread(target=background_worker, daemon=True).start()
|
| 578 |
|
|
@@ -676,9 +707,17 @@ with gr.Blocks(css=custom_css, theme=app_theme, title="SAM3 Segmentation") as de
|
|
| 676 |
with gr.Accordion("⚙️ Settings", open=True):
|
| 677 |
vid_frames = gr.Slider(
|
| 678 |
10, 500, 60, 10,
|
| 679 |
-
label="Max Frames (
|
| 680 |
-
info="
|
| 681 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 682 |
|
| 683 |
vid_submit = gr.Button("🚀 Submit Job (Background)", variant="primary", size="lg")
|
| 684 |
vid_check = gr.Button("🔍 Check Status", variant="secondary")
|
|
@@ -1019,9 +1058,15 @@ with gr.Blocks(css=custom_css, theme=app_theme, title="SAM3 Segmentation") as de
|
|
| 1019 |
""")
|
| 1020 |
|
| 1021 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1022 |
demo.launch(
|
| 1023 |
-
|
| 1024 |
-
|
| 1025 |
-
|
| 1026 |
-
show_error=True
|
|
|
|
| 1027 |
)
|
|
|
|
| 412 |
'duration': f"{(datetime.now() - start).total_seconds():.2f}s"
|
| 413 |
}
|
| 414 |
|
| 415 |
+
@spaces.GPU(duration=300)
|
| 416 |
def process_video_job(job):
|
| 417 |
start = datetime.now()
|
| 418 |
cap = cv2.VideoCapture(job['video'])
|
|
|
|
| 420 |
w, h = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 421 |
|
| 422 |
frames = []
|
| 423 |
+
limit = job.get('frame_limit', 60)
|
| 424 |
+
if limit == 0:
|
| 425 |
+
limit = 999999
|
| 426 |
+
|
| 427 |
count = 0
|
| 428 |
while cap.isOpened():
|
| 429 |
ret, frame = cap.read()
|
| 430 |
+
if not ret or count >= limit:
|
| 431 |
break
|
| 432 |
frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
|
| 433 |
count += 1
|
| 434 |
cap.release()
|
| 435 |
|
| 436 |
+
print(f"📹 Processing {len(frames)} frames...")
|
| 437 |
+
|
| 438 |
session = VID_PROCESSOR.init_video_session(video=frames, inference_device=device, dtype=torch.bfloat16)
|
| 439 |
session = VID_PROCESSOR.add_text_prompt(inference_session=session, text=job['prompt'])
|
| 440 |
|
|
|
|
| 449 |
]
|
| 450 |
|
| 451 |
total = len(frames)
|
| 452 |
+
processed = 0
|
| 453 |
+
|
| 454 |
for idx, out in enumerate(VID_MODEL.propagate_in_video_iterator(inference_session=session, max_frame_num_to_track=total)):
|
| 455 |
proc = VID_PROCESSOR.postprocess_outputs(session, out)
|
| 456 |
orig = Image.fromarray(frames[out.frame_idx])
|
|
|
|
| 487 |
writers[1].write(np.zeros((h, w, 3), dtype=np.uint8))
|
| 488 |
writers[2].write(orig_bgr)
|
| 489 |
|
| 490 |
+
processed += 1
|
| 491 |
+
progress = int((processed / total) * 100)
|
| 492 |
+
processing_results[job['id']]['progress'] = progress
|
| 493 |
+
|
| 494 |
+
# Log progress every 10%
|
| 495 |
+
if progress % 10 == 0:
|
| 496 |
+
print(f"⏳ Video progress: {progress}% ({processed}/{total} frames)")
|
| 497 |
|
| 498 |
for w in writers:
|
| 499 |
w.release()
|
| 500 |
|
| 501 |
+
print(f"✅ Video completed: {len(frames)} frames in {(datetime.now() - start).total_seconds():.2f}s")
|
| 502 |
+
|
| 503 |
return {
|
| 504 |
'output_path': out_path,
|
| 505 |
'mask_video_path': mask_path,
|
|
|
|
| 545 |
# ============ BACKGROUND WORKER ============
|
| 546 |
def background_worker():
|
| 547 |
while True:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 548 |
try:
|
| 549 |
+
job = processing_queue.get()
|
| 550 |
+
if job is None:
|
| 551 |
+
break
|
|
|
|
|
|
|
|
|
|
| 552 |
|
| 553 |
+
job_id = job['id']
|
| 554 |
+
job_type = job['type']
|
|
|
|
|
|
|
|
|
|
| 555 |
|
| 556 |
+
print(f"🚀 Starting job {job_id[:8]} - Type: {job_type}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 557 |
|
| 558 |
+
processing_results[job_id] = {'status': 'processing', 'progress': 0}
|
| 559 |
+
|
| 560 |
+
try:
|
| 561 |
+
if job_type == 'image':
|
| 562 |
+
result = process_image_job(job)
|
| 563 |
+
elif job_type == 'video':
|
| 564 |
+
result = process_video_job(job)
|
| 565 |
+
elif job_type == 'click':
|
| 566 |
+
result = process_click_job(job)
|
| 567 |
+
|
| 568 |
+
processing_results[job_id] = {
|
| 569 |
+
'status': 'completed',
|
| 570 |
+
'result': result,
|
| 571 |
+
'progress': 100
|
| 572 |
+
}
|
| 573 |
+
|
| 574 |
+
print(f"✅ Job {job_id[:8]} completed successfully")
|
| 575 |
+
|
| 576 |
+
save_history({
|
| 577 |
+
'id': job_id,
|
| 578 |
+
'type': job_type,
|
| 579 |
+
'prompt': job.get('prompt', 'N/A'),
|
| 580 |
+
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
| 581 |
+
'status': 'completed',
|
| 582 |
+
**result
|
| 583 |
+
})
|
| 584 |
+
|
| 585 |
+
except Exception as e:
|
| 586 |
+
print(f"❌ Job {job_id[:8]} failed: {str(e)}")
|
| 587 |
+
import traceback
|
| 588 |
+
traceback.print_exc()
|
| 589 |
+
|
| 590 |
+
processing_results[job_id] = {
|
| 591 |
+
'status': 'error',
|
| 592 |
+
'error': str(e),
|
| 593 |
+
'progress': 0
|
| 594 |
+
}
|
| 595 |
+
save_history({
|
| 596 |
+
'id': job_id,
|
| 597 |
+
'type': job_type,
|
| 598 |
+
'prompt': job.get('prompt', 'N/A'),
|
| 599 |
+
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
| 600 |
+
'status': 'error',
|
| 601 |
+
'error': str(e)
|
| 602 |
+
})
|
| 603 |
except Exception as e:
|
| 604 |
+
print(f"⚠️ Worker error: {e}")
|
| 605 |
+
import traceback
|
| 606 |
+
traceback.print_exc()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 607 |
|
| 608 |
threading.Thread(target=background_worker, daemon=True).start()
|
| 609 |
|
|
|
|
| 707 |
with gr.Accordion("⚙️ Settings", open=True):
|
| 708 |
vid_frames = gr.Slider(
|
| 709 |
10, 500, 60, 10,
|
| 710 |
+
label="Max Frames (Giảm để xử lý nhanh hơn)",
|
| 711 |
+
info="Khuyến nghị: 30-60 frames cho video ngắn, 100-200 cho video dài"
|
| 712 |
)
|
| 713 |
+
|
| 714 |
+
gr.Markdown("""
|
| 715 |
+
**💡 Lưu ý xử lý video:**
|
| 716 |
+
- Video chạy background, không bị timeout
|
| 717 |
+
- Nhấn "Check Status" để xem progress
|
| 718 |
+
- Video dài sẽ mất nhiều thời gian hơn
|
| 719 |
+
- Giảm số frames nếu muốn xử lý nhanh
|
| 720 |
+
""")
|
| 721 |
|
| 722 |
vid_submit = gr.Button("🚀 Submit Job (Background)", variant="primary", size="lg")
|
| 723 |
vid_check = gr.Button("🔍 Check Status", variant="secondary")
|
|
|
|
| 1058 |
""")
|
| 1059 |
|
| 1060 |
if __name__ == "__main__":
|
| 1061 |
+
print("🚀 Starting SAM3 Application...")
|
| 1062 |
+
print(f"📁 Output directory: {OUTPUTS_DIR}")
|
| 1063 |
+
print(f"📥 Downloads directory: {DOWNLOADS_DIR}")
|
| 1064 |
+
print(f"📊 History file: {HISTORY_FILE}")
|
| 1065 |
+
|
| 1066 |
demo.launch(
|
| 1067 |
+
server_name="0.0.0.0",
|
| 1068 |
+
server_port=7860,
|
| 1069 |
+
max_threads=10,
|
| 1070 |
+
show_error=True,
|
| 1071 |
+
share=False
|
| 1072 |
)
|