Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -720,6 +720,130 @@ def build_advanced_character_pack(
|
|
| 720 |
return [], None, None, None, traceback.format_exc()
|
| 721 |
|
| 722 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 723 |
def build_ui():
|
| 724 |
with gr.Blocks(title="SCAIL-Pose Pack Builder") as demo:
|
| 725 |
gr.Markdown(
|
|
@@ -788,6 +912,48 @@ def build_ui():
|
|
| 788 |
outputs=[simple_ref_mask, simple_rendered, simple_mask_video, simple_zip, simple_status],
|
| 789 |
)
|
| 790 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 791 |
with gr.Tab("Advanced Character Pack"):
|
| 792 |
gr.Markdown(
|
| 793 |
"Create a canonical SCAIL-2 animation pack for multi-reference or multi-character cases. "
|
|
@@ -869,8 +1035,8 @@ def build_ui():
|
|
| 869 |
|
| 870 |
with gr.Tab("Pack Format"):
|
| 871 |
gr.Markdown(
|
| 872 |
-
"The Simple Pack tab exports flat packs. The Advanced Character Pack
|
| 873 |
-
"character
|
| 874 |
"Simple animation pack:\n"
|
| 875 |
"```text\n"
|
| 876 |
"ref.png\n"
|
|
@@ -913,4 +1079,4 @@ def build_ui():
|
|
| 913 |
|
| 914 |
|
| 915 |
if __name__ == "__main__":
|
| 916 |
-
build_ui().queue(max_size=4).launch(show_error=True)
|
|
|
|
| 720 |
return [], None, None, None, traceback.format_exc()
|
| 721 |
|
| 722 |
|
| 723 |
+
@spaces.GPU(duration=GPU_DURATION, size=GPU_SIZE)
|
| 724 |
+
def build_two_character_pack(
|
| 725 |
+
character_0_ref,
|
| 726 |
+
character_1_ref,
|
| 727 |
+
driving_video,
|
| 728 |
+
prompt,
|
| 729 |
+
sam3_text,
|
| 730 |
+
progress=gr.Progress(track_tqdm=True),
|
| 731 |
+
):
|
| 732 |
+
try:
|
| 733 |
+
progress(0.0, desc="Checking SCAIL-Pose repo")
|
| 734 |
+
_require_repo_layout()
|
| 735 |
+
progress(0.04, desc="Preparing SAM3 weights")
|
| 736 |
+
sam3_model = _ensure_sam3_weights()
|
| 737 |
+
|
| 738 |
+
job_dir = _new_job_dir("two_character_animation")
|
| 739 |
+
pack_root = job_dir / "scail2_input_pack"
|
| 740 |
+
pack_root.mkdir(parents=True, exist_ok=True)
|
| 741 |
+
|
| 742 |
+
progress(0.08, desc="Preparing two-character inputs")
|
| 743 |
+
work_driving = job_dir / "driving.mp4"
|
| 744 |
+
_copy_video(driving_video, work_driving, "driving video")
|
| 745 |
+
shutil.copy2(work_driving, pack_root / "rendered_v2.mp4")
|
| 746 |
+
|
| 747 |
+
prepared_refs = [
|
| 748 |
+
CharacterRef(
|
| 749 |
+
character="character_0",
|
| 750 |
+
view="front",
|
| 751 |
+
path=_copy_reference_to_png(_as_path(character_0_ref, "character 0 reference"), pack_root / "characters" / "character_0" / "front.png"),
|
| 752 |
+
),
|
| 753 |
+
CharacterRef(
|
| 754 |
+
character="character_1",
|
| 755 |
+
view="front",
|
| 756 |
+
path=_copy_reference_to_png(_as_path(character_1_ref, "character 1 reference"), pack_root / "characters" / "character_1" / "front.png"),
|
| 757 |
+
),
|
| 758 |
+
]
|
| 759 |
+
character_names = ["character_0", "character_1"]
|
| 760 |
+
primary = prepared_refs[0]
|
| 761 |
+
_build_advanced_metadata(pack_root, "animation", prompt, primary, character_names, sam3_text)
|
| 762 |
+
|
| 763 |
+
progress(0.12, desc="Loading SAM3 predictors")
|
| 764 |
+
video_predictor, image_predictor = _load_sam3_predictors(sam3_model)
|
| 765 |
+
|
| 766 |
+
from TrackSam3.track import get_mask_from_image, get_mask_from_image_via_video, get_mask_from_video
|
| 767 |
+
from NLFPoseExtract.v2_helper import save_colored_mask_image, write_colored_mask_video
|
| 768 |
+
|
| 769 |
+
text = _text_args(sam3_text)
|
| 770 |
+
colors = SCAIL_COLORS[:2]
|
| 771 |
+
|
| 772 |
+
progress(0.20, desc="Tracking two subjects left-to-right")
|
| 773 |
+
drv_masks, drv_colors = get_mask_from_video(
|
| 774 |
+
str(work_driving),
|
| 775 |
+
video_predictor,
|
| 776 |
+
max_targets=2,
|
| 777 |
+
sort_by="x",
|
| 778 |
+
fixed_colors=colors,
|
| 779 |
+
text=text,
|
| 780 |
+
)
|
| 781 |
+
if len(drv_masks) < 2:
|
| 782 |
+
raise RuntimeError(
|
| 783 |
+
f"SAM3 detected {len(drv_masks)} subject(s), but this mode expects two. "
|
| 784 |
+
"Try a clearer driving video or use the Advanced Character Pack."
|
| 785 |
+
)
|
| 786 |
+
|
| 787 |
+
import cv2
|
| 788 |
+
|
| 789 |
+
cap = cv2.VideoCapture(str(work_driving))
|
| 790 |
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
| 791 |
+
cap.release()
|
| 792 |
+
fps_int = max(1, int(round(fps or 24)))
|
| 793 |
+
write_colored_mask_video(
|
| 794 |
+
drv_masks[:2],
|
| 795 |
+
drv_colors[:2],
|
| 796 |
+
str(pack_root / "rendered_mask_v2.mp4"),
|
| 797 |
+
fps_int,
|
| 798 |
+
bg_color=(255, 255, 255),
|
| 799 |
+
)
|
| 800 |
+
|
| 801 |
+
progress(0.55, desc="Generating reference masks")
|
| 802 |
+
logs = [
|
| 803 |
+
"Mode: two-character animation",
|
| 804 |
+
"Mapping: left-most driving subject -> character_0; next subject to the right -> character_1",
|
| 805 |
+
f"Driving tracks detected: {len(drv_masks)}",
|
| 806 |
+
]
|
| 807 |
+
for ref in prepared_refs:
|
| 808 |
+
character_idx = character_names.index(ref.character)
|
| 809 |
+
color = drv_colors[character_idx]
|
| 810 |
+
mask_path = ref.path.with_name(f"{ref.view}_mask.png")
|
| 811 |
+
progress(None, desc=f"Masking {ref.character}")
|
| 812 |
+
try:
|
| 813 |
+
ref_masks, _ = get_mask_from_image_via_video(
|
| 814 |
+
str(ref.path),
|
| 815 |
+
video_predictor,
|
| 816 |
+
max_targets=1,
|
| 817 |
+
sort_by="x",
|
| 818 |
+
fixed_colors=[color],
|
| 819 |
+
text=text,
|
| 820 |
+
)
|
| 821 |
+
except Exception as exc:
|
| 822 |
+
logging.warning("Video-based image masking failed for %s, trying image predictor: %s", ref.path, exc)
|
| 823 |
+
ref_masks, _ = get_mask_from_image(
|
| 824 |
+
str(ref.path),
|
| 825 |
+
image_predictor,
|
| 826 |
+
max_targets=1,
|
| 827 |
+
sort_by="x",
|
| 828 |
+
fixed_colors=[color],
|
| 829 |
+
text=text,
|
| 830 |
+
)
|
| 831 |
+
if len(ref_masks) == 0:
|
| 832 |
+
raise RuntimeError(f"SAM3 did not detect a subject in {ref.character}.")
|
| 833 |
+
save_colored_mask_image([ref_masks[0]], [color], str(mask_path), bg_color=(255, 255, 255))
|
| 834 |
+
logs.append(f"- {ref.character}: {mask_path.relative_to(pack_root).as_posix()}")
|
| 835 |
+
|
| 836 |
+
progress(0.92, desc="Packaging two-character pack")
|
| 837 |
+
zip_path = _write_canonical_pack_zip(pack_root, "two_character_animation")
|
| 838 |
+
gallery = _advanced_gallery(pack_root)
|
| 839 |
+
progress(1.0, desc="Done")
|
| 840 |
+
status = f"Done. Two-character pack created at {zip_path}\n\n" + "\n".join(logs)
|
| 841 |
+
return gallery, str(pack_root / "rendered_v2.mp4"), str(pack_root / "rendered_mask_v2.mp4"), str(zip_path), status
|
| 842 |
+
except Exception:
|
| 843 |
+
logging.exception("Two-character pack generation failed")
|
| 844 |
+
return [], None, None, None, traceback.format_exc()
|
| 845 |
+
|
| 846 |
+
|
| 847 |
def build_ui():
|
| 848 |
with gr.Blocks(title="SCAIL-Pose Pack Builder") as demo:
|
| 849 |
gr.Markdown(
|
|
|
|
| 912 |
outputs=[simple_ref_mask, simple_rendered, simple_mask_video, simple_zip, simple_status],
|
| 913 |
)
|
| 914 |
|
| 915 |
+
with gr.Tab("Two Characters"):
|
| 916 |
+
gr.Markdown(
|
| 917 |
+
"Fast path for the common two-character case. Upload two reference images and one driving video. "
|
| 918 |
+
"`Character 0` should match the left-most subject in the driving video; `Character 1` should match "
|
| 919 |
+
"the next subject to the right."
|
| 920 |
+
)
|
| 921 |
+
with gr.Row():
|
| 922 |
+
two_char0 = gr.Image(type="filepath", label="Character 0 reference")
|
| 923 |
+
two_char1 = gr.Image(type="filepath", label="Character 1 reference")
|
| 924 |
+
two_driving = gr.Video(label="Driving video")
|
| 925 |
+
with gr.Accordion("Optional settings", open=False):
|
| 926 |
+
two_prompt = gr.Textbox(label="Prompt for SCAIL-2", lines=3)
|
| 927 |
+
two_sam3_text = gr.Textbox(
|
| 928 |
+
value="human character",
|
| 929 |
+
label="SAM3 text prompt",
|
| 930 |
+
info="Use a broad prompt such as `human character`, or comma/newline-separated prompts for distinct subjects.",
|
| 931 |
+
)
|
| 932 |
+
two_run = gr.Button("Generate two-character pack", variant="primary")
|
| 933 |
+
two_gallery = gr.Gallery(
|
| 934 |
+
label="References and generated masks",
|
| 935 |
+
columns=4,
|
| 936 |
+
height=320,
|
| 937 |
+
selected_index=0,
|
| 938 |
+
preview=True,
|
| 939 |
+
)
|
| 940 |
+
with gr.Row():
|
| 941 |
+
two_rendered = gr.Video(label="Rendered / driving video")
|
| 942 |
+
two_mask_video = gr.Video(label="Driving mask video")
|
| 943 |
+
two_zip = gr.File(label="Download two-character SCAIL-2 pack")
|
| 944 |
+
two_status = gr.Textbox(label="Run logs", lines=14)
|
| 945 |
+
two_run.click(
|
| 946 |
+
build_two_character_pack,
|
| 947 |
+
inputs=[
|
| 948 |
+
two_char0,
|
| 949 |
+
two_char1,
|
| 950 |
+
two_driving,
|
| 951 |
+
two_prompt,
|
| 952 |
+
two_sam3_text,
|
| 953 |
+
],
|
| 954 |
+
outputs=[two_gallery, two_rendered, two_mask_video, two_zip, two_status],
|
| 955 |
+
)
|
| 956 |
+
|
| 957 |
with gr.Tab("Advanced Character Pack"):
|
| 958 |
gr.Markdown(
|
| 959 |
"Create a canonical SCAIL-2 animation pack for multi-reference or multi-character cases. "
|
|
|
|
| 1035 |
|
| 1036 |
with gr.Tab("Pack Format"):
|
| 1037 |
gr.Markdown(
|
| 1038 |
+
"The Simple Pack tab exports flat packs. The Two Characters and Advanced Character Pack tabs "
|
| 1039 |
+
"export canonical character packs with explicit character folders.\n\n"
|
| 1040 |
"Simple animation pack:\n"
|
| 1041 |
"```text\n"
|
| 1042 |
"ref.png\n"
|
|
|
|
| 1079 |
|
| 1080 |
|
| 1081 |
if __name__ == "__main__":
|
| 1082 |
+
build_ui().queue(max_size=4).launch(show_error=True)
|