Shalmoni commited on
Commit
0d7b5a8
·
verified ·
1 Parent(s): eaf5aa0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +137 -28
app.py CHANGED
@@ -1,15 +1,119 @@
1
- import gradio as gr
2
  from PIL import Image
 
 
 
 
 
 
3
 
4
- def generate_fn(prompt, image):
5
- # Stub: echo for now (swap with real gen later)
 
 
 
 
 
 
 
6
  if not prompt and image is None:
7
- raise gr.Error("Add a prompt or an image.")
8
- msg = f"OK ✅ Prompt length: {len(prompt or '')} | Image: {'yes' if image else 'no'}"
9
- return msg
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  CUSTOM_CSS = """
12
- /* Page padding & grid background hint (optional) */
13
  .gradio-container { padding: 24px; }
14
 
15
  /* Big rounded prompt box */
@@ -22,18 +126,15 @@ CUSTOM_CSS = """
22
  }
23
 
24
  /* Rounded square image card */
25
- #add-image .wrap.svelte-1ipelgc,
26
- #add-image .wrap { /* compat across versions */
27
- border-radius: 28px !important;
28
- }
29
- #add-image .input-image,
30
- #add-image .empty.svelte-1ipelgc {
31
  border-radius: 28px !important;
32
  min-width: 240px;
33
  min-height: 240px;
34
  }
35
 
36
- /* Pill generate button (right side) */
37
  #gen-btn button {
38
  border-radius: 999px !important;
39
  padding: 12px 24px;
@@ -41,30 +142,38 @@ CUSTOM_CSS = """
41
  }
42
  """
43
 
44
- with gr.Blocks(css=CUSTOM_CSS, title="StitchMaster – UI Sketch") as demo:
 
 
 
45
  # Row 1: Big rounded prompt input
46
- prompt = gr.Textbox(
47
  label=None,
48
  placeholder="Prompt input",
49
  lines=8,
50
  elem_id="prompt-box"
51
  )
52
 
53
- # Row 2: Left = Add Image card | Right = Generate button
54
  with gr.Row():
55
  with gr.Column(scale=1, min_width=300):
56
- img = gr.Image(
57
- label="Add Image",
58
- type="pil",
59
- elem_id="add-image"
60
- )
61
  with gr.Column(scale=3, min_width=300):
62
- # Spacer effect using Markdown (empty height) then right-aligned button
63
- gr.Markdown(" \n\n ", elem_classes=["spacer"])
64
- gen = gr.Button("Generate", elem_id="gen-btn")
 
65
 
66
- out = gr.Markdown("") # simple status/output for now
67
- gen.click(fn=generate_fn, inputs=[prompt, img], outputs=out)
 
 
 
 
 
 
 
 
68
 
69
  if __name__ == "__main__":
70
- demo.launch()
 
1
+ import io, uuid, base64, requests, random
2
  from PIL import Image
3
+ import gradio as gr
4
+
5
+ # ====== CONFIG ======
6
+ # Your Modal endpoint (POST with multipart body; prompt/seed in querystring)
7
+ MODAL_URL = "https://moonmath-ai--moonmath-i2v-backend-moonmathinference-run.modal.run"
8
+ REQUEST_TIMEOUT_SEC = 600 # adjust if your backend needs longer
9
 
10
+ # ====== BACKEND CALLER ======
11
+ def call_modal_backend(prompt: str, image: Image.Image | None, seed: int | None):
12
+ """
13
+ Sends prompt + optional image to the Modal backend.
14
+ Accepts:
15
+ - raw MP4 bytes response
16
+ - JSON with video_url or base64 video
17
+ Returns a path or URL usable by gr.Video.
18
+ """
19
  if not prompt and image is None:
20
+ raise gr.Error("Please provide a prompt or upload an image.")
21
+
22
+ # Build multipart body if image provided
23
+ files = None
24
+ if image is not None:
25
+ buf = io.BytesIO()
26
+ image.save(buf, format="PNG") # change to JPEG if your backend expects it
27
+ buf.seek(0)
28
+ files = {"image_bytes": ("input.png", buf, "image/png")}
29
+
30
+ # Query string params
31
+ params = {}
32
+ if prompt:
33
+ params["prompt"] = prompt
34
+ if seed is not None:
35
+ params["seed"] = str(seed)
36
+
37
+ # Perform request
38
+ res = requests.post(
39
+ MODAL_URL,
40
+ params=params,
41
+ files=files,
42
+ headers={"accept": "application/json"},
43
+ timeout=REQUEST_TIMEOUT_SEC,
44
+ )
45
+ res.raise_for_status()
46
+
47
+ ctype = (res.headers.get("content-type") or "").lower()
48
+
49
+ # 1) Raw MP4 bytes directly
50
+ if "video/mp4" in ctype or ctype.startswith("application/octet-stream"):
51
+ mp4_path = f"out_{uuid.uuid4().hex[:8]}.mp4"
52
+ with open(mp4_path, "wb") as f:
53
+ f.write(res.content)
54
+ return mp4_path
55
+
56
+ # 2) JSON (URL or base64)
57
+ if "application/json" in ctype:
58
+ data = res.json()
59
+ url = data.get("video_url") or data.get("url") or data.get("result", {}).get("video_url")
60
+ if url:
61
+ return url # gr.Video can stream a URL
62
+
63
+ b64 = (
64
+ data.get("video_b64")
65
+ or data.get("video_bytes")
66
+ or data.get("result", {}).get("video_b64")
67
+ )
68
+ if b64:
69
+ if "," in b64: # strip data: header if present
70
+ b64 = b64.split(",", 1)[1]
71
+ blob = base64.b64decode(b64)
72
+ mp4_path = f"out_{uuid.uuid4().hex[:8]}.mp4"
73
+ with open(mp4_path, "wb") as f:
74
+ f.write(blob)
75
+ return mp4_path
76
 
77
+ raise gr.Error(f"Backend JSON did not contain a video field. Keys: {list(data.keys())}")
78
+
79
+ # 3) Fallback: write bytes as mp4
80
+ mp4_path = f"out_{uuid.uuid4().hex[:8]}.mp4"
81
+ with open(mp4_path, "wb") as f:
82
+ f.write(res.content)
83
+ return mp4_path
84
+
85
+ # ====== UI CALLBACK ======
86
+ def on_generate(prompt, image, seed, lock_longshot):
87
+ """
88
+ lock_longshot is included so you can later inject constraints server-side if needed.
89
+ For now it simply forwards prompt & image to your Modal backend.
90
+ """
91
+ # If user left seed blank, generate one
92
+ if seed is None or str(seed).strip() == "":
93
+ seed_val = random.randint(0, 2**31 - 1)
94
+ else:
95
+ # Gradio Number returns float; cast safely
96
+ try:
97
+ seed_val = int(seed)
98
+ except Exception:
99
+ seed_val = random.randint(0, 2**31 - 1)
100
+
101
+ # (Optional) reinforce long-shot constraints in prompt (safe no-op if you don’t need it)
102
+ if lock_longshot and prompt:
103
+ musts = [
104
+ "single continuous long shot",
105
+ "no cuts, no new shot, no angle switch",
106
+ "smooth camera motion (pan/tilt/zoom only)",
107
+ "unbroken continuity"
108
+ ]
109
+ prompt = prompt.strip() + ". " + "; ".join(musts)
110
+
111
+ video_path_or_url = call_modal_backend(prompt, image, seed_val)
112
+ info = f"Seed: {seed_val}"
113
+ return video_path_or_url, info
114
+
115
+ # ====== STYLE ======
116
  CUSTOM_CSS = """
 
117
  .gradio-container { padding: 24px; }
118
 
119
  /* Big rounded prompt box */
 
126
  }
127
 
128
  /* Rounded square image card */
129
+ #add-image .wrap,
130
+ #add-image .input-image,
131
+ #add-image .empty {
 
 
 
132
  border-radius: 28px !important;
133
  min-width: 240px;
134
  min-height: 240px;
135
  }
136
 
137
+ /* Pill generate button */
138
  #gen-btn button {
139
  border-radius: 999px !important;
140
  padding: 12px 24px;
 
142
  }
143
  """
144
 
145
+ # ====== APP ======
146
+ with gr.Blocks(css=CUSTOM_CSS, title="Stitch UI – Modal Hook") as demo:
147
+ gr.Markdown("### Stitch – turn prompt/image into a generated video (Modal backend)")
148
+
149
  # Row 1: Big rounded prompt input
150
+ prompt_tb = gr.Textbox(
151
  label=None,
152
  placeholder="Prompt input",
153
  lines=8,
154
  elem_id="prompt-box"
155
  )
156
 
157
+ # Row 2: Left image card, right controls (seed + generate)
158
  with gr.Row():
159
  with gr.Column(scale=1, min_width=300):
160
+ img_in = gr.Image(label="Add Image", type="pil", elem_id="add-image")
 
 
 
 
161
  with gr.Column(scale=3, min_width=300):
162
+ with gr.Row():
163
+ seed_in = gr.Number(value=None, label="Seed (optional)")
164
+ lock_long = gr.Checkbox(value=True, label="Lock camera (long shot, no cuts)")
165
+ gen_btn = gr.Button("Generate", elem_id="gen-btn")
166
 
167
+ # Output
168
+ with gr.Row():
169
+ video_out = gr.Video(label="Output Video", interactive=False, autoplay=True)
170
+ info_out = gr.Markdown("")
171
+
172
+ gen_btn.click(
173
+ fn=on_generate,
174
+ inputs=[prompt_tb, img_in, seed_in, lock_long],
175
+ outputs=[video_out, info_out]
176
+ )
177
 
178
  if __name__ == "__main__":
179
+ demo.launch()