Stiphan commited on
Commit
8bcbcec
·
verified ·
1 Parent(s): c808131

Add Image Animator (image-to-video) feature

Browse files

Added new Image Animator tab: allows user to upload image + animation prompt, calls Hugging Face image-to-video model and returns animated mp4 output. Requires HF API key.

Files changed (1) hide show
  1. image_animator.py +108 -0
image_animator.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import time
4
+ from io import BytesIO
5
+
6
+ # ---------- CONFIG ----------
7
+ HF_API_TOKEN = "YOUR_HF_API_KEY" # <-- yaha apna Hugging Face token daalna
8
+ # Example image->video model (change if you prefer another)
9
+ IMAGE_TO_VIDEO_MODEL = "ali-vilab/image-to-video"
10
+ API_URL = f"https://api-inference.huggingface.co/models/{IMAGE_TO_VIDEO_MODEL}"
11
+ HEADERS = {"Authorization": f"Bearer {HF_API_TOKEN}"}
12
+ # ----------------------------
13
+
14
+ def call_image_to_video_api(image_bytes, prompt, steps=20):
15
+ """
16
+ Calls HF image->video model as multipart (image file + prompt).
17
+ Returns bytes (video) on success or None on failure.
18
+ """
19
+ files = {
20
+ "image": ("upload.png", image_bytes, "image/png")
21
+ }
22
+ data = {
23
+ "prompt": prompt,
24
+ # optional params: model-specific, keep conservative defaults
25
+ "num_inference_steps": str(steps)
26
+ }
27
+ try:
28
+ resp = requests.post(API_URL, headers=HEADERS, files=files, data=data, timeout=120)
29
+ except Exception as e:
30
+ return None, f"Request failed: {e}"
31
+
32
+ if resp.status_code == 200:
33
+ # Some HF inference endpoints return raw bytes, some return JSON with base64.
34
+ # Try to detect common cases.
35
+ content_type = resp.headers.get("content-type", "")
36
+ if "video" in content_type or content_type.startswith("application/octet-stream"):
37
+ return resp.content, None
38
+ # If JSON with base64
39
+ try:
40
+ js = resp.json()
41
+ if isinstance(js, dict) and "video" in js:
42
+ import base64
43
+ video_b64 = js["video"]
44
+ return base64.b64decode(video_b64), None
45
+ except Exception:
46
+ pass
47
+ return None, f"Unexpected response content-type: {content_type}"
48
+ else:
49
+ # try parse error
50
+ try:
51
+ err = resp.json()
52
+ except Exception:
53
+ err = resp.text
54
+ return None, f"Model error {resp.status_code}: {err}"
55
+
56
+ def generate_animation(image, prompt, steps, progress=gr.Progress()):
57
+ """
58
+ Gradio handler: takes uploaded image (PIL or np array), prompt string and returns video bytes or error message.
59
+ """
60
+ if image is None or prompt is None or prompt.strip() == "":
61
+ return None, "Please upload an image and write a descriptive animation prompt."
62
+
63
+ # convert PIL image to PNG bytes
64
+ if hasattr(image, "save"):
65
+ img_bytes = BytesIO()
66
+ image.save(img_bytes, format="PNG")
67
+ img_bytes = img_bytes.getvalue()
68
+ else:
69
+ return None, "Invalid image format."
70
+
71
+ progress(0, desc="Sending to model...")
72
+ # call external HF model (this may take time)
73
+ video_bytes, error = call_image_to_video_api(img_bytes, prompt, steps=int(steps))
74
+ if error:
75
+ return None, f"Failed: {error}"
76
+
77
+ progress(100, desc="Done")
78
+ return BytesIO(video_bytes), None
79
+
80
+ # ----- Gradio UI ----- (standalone tab / app)
81
+ with gr.Blocks() as demo:
82
+ gr.Markdown("<h2 style='text-align:center'>✨ Image Animator — SaEdit MultiAi</h2>")
83
+ with gr.Row():
84
+ with gr.Column(scale=2):
85
+ upload = gr.Image(type="pil", label="Upload image (face/portrait/scene)")
86
+ prompt = gr.Textbox(label="Describe animation (e.g. 'slow zoom-in, gentle sparkles, 3s loop')", lines=3)
87
+ steps = gr.Slider(minimum=5, maximum=50, step=1, value=18, label="Inference steps (quality → speed)")
88
+ generate_btn = gr.Button("Generate Animation")
89
+ note = gr.Markdown(
90
+ "⚠️ Note: This uses a remote image→video model. Processing may take from 30s to a few minutes depending on model and load."
91
+ )
92
+ with gr.Column(scale=3):
93
+ out_video = gr.Video(label="Animated output (mp4)")
94
+ status = gr.Textbox(label="Status / Errors", interactive=False)
95
+
96
+ def on_click_generate(img, txt, steps_val):
97
+ video_file, err = generate_animation(img, txt, steps_val)
98
+ if err:
99
+ return None, str(err)
100
+ # return an IO object readable by Gradio as a video
101
+ return video_file, "Success — download below."
102
+
103
+ generate_btn.click(on_click_generate, [upload, prompt, steps], [out_video, status])
104
+
105
+ # If you want to use this as a module and import the Blocks into your main app,
106
+ # you can import demo and place it into a Tab.
107
+ if __name__ == "__main__":
108
+ demo.launch()