akhaliq HF Staff commited on
Commit
2122b85
·
verified ·
1 Parent(s): 10ed921

Update Gradio app with multiple files

Browse files
Files changed (1) hide show
  1. app.py +176 -112
app.py CHANGED
@@ -3,37 +3,69 @@ import os
3
  from huggingface_hub import InferenceClient
4
  from pathlib import Path
5
  import tempfile
 
 
 
6
 
7
- # Initialize the inference client
8
- client = InferenceClient(
9
- provider="fal-ai",
10
- api_key=os.environ.get("HF_TOKEN"),
11
- bill_to="huggingface",
12
- )
13
 
14
- def generate_video_with_auth(image, prompt, profile: gr.OAuthProfile | None, progress=gr.Progress()):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  """
16
  Generate a video from an image using the Ovi model with authentication check.
17
 
18
  Args:
19
  image: Input image (PIL Image or file path)
20
  prompt: Text prompt describing the desired motion/animation
21
- profile: OAuth profile for authentication
22
  progress: Gradio progress tracker
23
 
24
  Returns:
25
- Path to the generated video file
26
  """
27
- if profile is None:
28
- raise gr.Error("Click Sign in with Hugging Face button to use this app for free")
29
-
30
- if image is None:
31
- raise gr.Error("Please upload an image first!")
32
-
33
- if not prompt or prompt.strip() == "":
34
- raise gr.Error("Please enter a prompt describing the desired motion!")
35
-
36
  try:
 
 
 
 
 
 
 
 
 
 
 
37
  progress(0.2, desc="Processing image...")
38
 
39
  # Read the image file
@@ -42,114 +74,125 @@ def generate_video_with_auth(image, prompt, profile: gr.OAuthProfile | None, pro
42
  input_image = image_file.read()
43
  else:
44
  # If image is a PIL Image, save it temporarily
45
- temp_image = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
46
- image.save(temp_image.name)
47
- with open(temp_image.name, "rb") as image_file:
48
- input_image = image_file.read()
 
 
 
 
 
 
 
 
 
 
49
 
50
  progress(0.4, desc="Generating video with AI...")
51
 
 
 
 
52
  # Generate video using the inference client
53
- video = client.image_to_video(
54
- input_image,
55
- prompt=prompt,
56
- model="chetwinlow1/Ovi",
57
- )
 
 
 
 
 
 
58
 
59
  progress(0.9, desc="Finalizing video...")
60
 
61
  # Save the video to a temporary file
62
- output_path = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
63
-
64
- # Check if video is bytes or a file path
65
- if isinstance(video, bytes):
66
- with open(output_path.name, "wb") as f:
67
- f.write(video)
68
- elif isinstance(video, str) and os.path.exists(video):
69
- # If it's a path, copy it
70
- import shutil
71
- shutil.copy(video, output_path.name)
72
- else:
73
- # Try to write it directly
74
- with open(output_path.name, "wb") as f:
75
- f.write(video)
76
 
77
  progress(1.0, desc="Complete!")
78
 
79
- return output_path.name
80
 
 
 
81
  except Exception as e:
82
- raise gr.Error(f"Error generating video: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
  # Create the Gradio interface
85
- with gr.Blocks(
86
- theme=gr.themes.Soft(
87
- primary_hue="blue",
88
- secondary_hue="indigo",
89
- ),
90
- css="""
91
- .header-link {
92
- font-size: 0.9em;
93
- color: #666;
94
- text-decoration: none;
95
- margin-bottom: 1em;
96
- display: inline-block;
97
- }
98
- .header-link:hover {
99
- color: #333;
100
- text-decoration: underline;
101
- }
102
- .main-header {
103
- text-align: center;
104
- margin-bottom: 2em;
105
- }
106
- .info-box {
107
- background-color: #f0f7ff;
108
- border-left: 4px solid #4285f4;
109
- padding: 1em;
110
- margin: 1em 0;
111
- border-radius: 4px;
112
- }
113
- .auth-warning {
114
- color: #ff6b00;
115
- font-weight: bold;
116
- text-align: center;
117
- margin: 1em 0;
118
- }
119
- """,
120
- title="Image to Video Generator with Ovi",
121
- ) as demo:
122
 
123
  gr.HTML(
124
  """
125
- <div class="main-header">
126
- <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="header-link">
127
- Built with anycoder
128
- </a>
 
 
 
 
 
 
 
 
 
129
  </div>
130
  """
131
  )
132
 
133
  gr.Markdown(
134
  """
135
- # 🎬 Image to Video Generator with Ovi
136
-
137
- Transform your static images into dynamic videos with synchronized audio using AI! Upload an image and describe the motion you want to see.
138
-
139
  Powered by Ovi: Twin Backbone Cross-Modal Fusion for Audio-Video Generation via [HuggingFace Inference Providers](https://huggingface.co/docs/huggingface_hub/guides/inference).
140
  """
141
  )
142
 
143
- gr.HTML(
144
- """
145
- <div class="auth-warning">
146
- ⚠️ You must Sign in with Hugging Face using the button below to use this app.
147
- </div>
148
- """
149
- )
150
-
151
  # Add login button - required for OAuth
152
- gr.LoginButton()
153
 
154
  gr.HTML(
155
  """
@@ -185,25 +228,33 @@ with gr.Blocks(
185
  with gr.Column(scale=1):
186
  image_input = gr.Image(
187
  label="📸 Upload Image",
188
- type="filepath",
189
- sources=["upload", "clipboard"],
190
  height=400,
191
  )
192
 
193
  prompt_input = gr.Textbox(
194
  label="✍️ Text Prompt",
195
  lines=3,
 
196
  )
197
 
198
- generate_btn = gr.Button(
199
- "🎬 Generate Video",
200
- variant="primary",
201
- size="lg",
202
- )
 
 
 
 
 
 
203
 
204
- clear_btn = gr.Button(
205
- "🗑️ Clear",
206
- variant="secondary",
 
 
207
  )
208
 
209
  gr.Examples(
@@ -222,6 +273,7 @@ with gr.Blocks(
222
  label="🎥 Generated Video",
223
  height=400,
224
  autoplay=True,
 
225
  )
226
 
227
  gr.Markdown(
@@ -245,17 +297,18 @@ with gr.Blocks(
245
  # Event handlers with authentication
246
  generate_btn.click(
247
  fn=generate_video_with_auth,
248
- inputs=[image_input, prompt_input],
249
- outputs=[video_output],
 
250
  queue=False,
251
  api_name=False,
252
  show_api=False,
253
  )
254
 
255
  clear_btn.click(
256
- fn=lambda: (None, "", None),
257
- inputs=None,
258
- outputs=[image_input, prompt_input, video_output],
259
  queue=False,
260
  )
261
 
@@ -288,8 +341,19 @@ with gr.Blocks(
288
 
289
  # Launch the app
290
  if __name__ == "__main__":
 
 
 
 
 
 
 
 
291
  demo.launch(
292
  show_api=False,
 
 
293
  enable_monitoring=False,
294
  quiet=True,
 
295
  )
 
3
  from huggingface_hub import InferenceClient
4
  from pathlib import Path
5
  import tempfile
6
+ import time
7
+ from typing import Optional
8
+ import shutil
9
 
10
+ # -------------------------
11
+ # Utilities
12
+ # -------------------------
 
 
 
13
 
14
+ def cleanup_temp_files():
15
+ try:
16
+ temp_dir = tempfile.gettempdir()
17
+ for file_path in Path(temp_dir).glob("*.mp4"):
18
+ try:
19
+ if file_path.stat().st_mtime < (time.time() - 300):
20
+ file_path.unlink(missing_ok=True)
21
+ except Exception:
22
+ pass
23
+ except Exception as e:
24
+ print(f"Cleanup error: {e}")
25
+
26
+ def _client_from_token(token: Optional[str]) -> InferenceClient:
27
+ if not token:
28
+ raise gr.Error("Please sign in first. This app requires your Hugging Face login.")
29
+ # IMPORTANT: do not set bill_to when using user OAuth tokens
30
+ return InferenceClient(
31
+ provider="fal-ai",
32
+ api_key=token,
33
+ )
34
+
35
+ def _save_bytes_as_temp_mp4(data: bytes) -> str:
36
+ temp_file = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
37
+ try:
38
+ temp_file.write(data)
39
+ temp_file.flush()
40
+ return temp_file.name
41
+ finally:
42
+ temp_file.close()
43
+
44
+ def generate_video_with_auth(image, prompt, token: gr.OAuthToken | None, progress=gr.Progress()):
45
  """
46
  Generate a video from an image using the Ovi model with authentication check.
47
 
48
  Args:
49
  image: Input image (PIL Image or file path)
50
  prompt: Text prompt describing the desired motion/animation
51
+ token: OAuth token for authentication
52
  progress: Gradio progress tracker
53
 
54
  Returns:
55
+ Tuple of (video_path, status_message)
56
  """
 
 
 
 
 
 
 
 
 
57
  try:
58
+ if token is None or not getattr(token, "token", None):
59
+ return None, "❌ Sign in with Hugging Face to continue. This app uses your inference provider credits."
60
+
61
+ if image is None:
62
+ return None, "Please upload an image first!"
63
+
64
+ if not prompt or prompt.strip() == "":
65
+ return None, "Please enter a prompt describing the desired motion!"
66
+
67
+ cleanup_temp_files()
68
+
69
  progress(0.2, desc="Processing image...")
70
 
71
  # Read the image file
 
74
  input_image = image_file.read()
75
  else:
76
  # If image is a PIL Image, save it temporarily
77
+ import io
78
+ from PIL import Image as PILImage
79
+
80
+ # Convert to bytes if necessary
81
+ if isinstance(image, PILImage.Image):
82
+ buffer = io.BytesIO()
83
+ image.save(buffer, format='PNG')
84
+ input_image = buffer.getvalue()
85
+ else:
86
+ # Assume it's a numpy array or similar
87
+ pil_image = PILImage.fromarray(image)
88
+ buffer = io.BytesIO()
89
+ pil_image.save(buffer, format='PNG')
90
+ input_image = buffer.getvalue()
91
 
92
  progress(0.4, desc="Generating video with AI...")
93
 
94
+ # Create client with user's token
95
+ client = _client_from_token(token.token)
96
+
97
  # Generate video using the inference client
98
+ try:
99
+ video = client.image_to_video(
100
+ input_image,
101
+ prompt=prompt,
102
+ model="chetwinlow1/Ovi",
103
+ )
104
+ except Exception as e:
105
+ import requests
106
+ if isinstance(e, requests.HTTPError) and getattr(e.response, "status_code", None) == 403:
107
+ return None, "❌ Access denied by provider (403). Make sure your HF account has credits/permission for provider 'fal-ai' and model 'chetwinlow1/Ovi'."
108
+ raise
109
 
110
  progress(0.9, desc="Finalizing video...")
111
 
112
  # Save the video to a temporary file
113
+ video_path = _save_bytes_as_temp_mp4(video)
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
  progress(1.0, desc="Complete!")
116
 
117
+ return video_path, f"✅ Video generated successfully with motion: '{prompt[:50]}...'"
118
 
119
+ except gr.Error as e:
120
+ return None, f"❌ {str(e)}"
121
  except Exception as e:
122
+ return None, f" Generation failed. If this keeps happening, check your provider quota or try again later."
123
+
124
+ def clear_inputs():
125
+ """Clear all inputs and outputs"""
126
+ return None, "", None, ""
127
+
128
+ # Custom CSS for better styling
129
+ custom_css = """
130
+ .container {
131
+ max-width: 1200px;
132
+ margin: auto;
133
+ }
134
+ .header-link {
135
+ text-decoration: none;
136
+ color: #2196F3;
137
+ font-weight: bold;
138
+ }
139
+ .header-link:hover {
140
+ text-decoration: underline;
141
+ }
142
+ .status-box {
143
+ padding: 10px;
144
+ border-radius: 5px;
145
+ margin-top: 10px;
146
+ }
147
+ .notice {
148
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
149
+ color: white;
150
+ padding: 14px 16px;
151
+ border-radius: 12px;
152
+ margin: 18px auto 6px;
153
+ max-width: 860px;
154
+ text-align: center;
155
+ font-size: 0.98rem;
156
+ }
157
+ .info-box {
158
+ background-color: #f0f7ff;
159
+ border-left: 4px solid #4285f4;
160
+ padding: 1em;
161
+ margin: 1em 0;
162
+ border-radius: 4px;
163
+ }
164
+ """
165
 
166
  # Create the Gradio interface
167
+ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="Image to Video Generator with Ovi (Paid)") as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
  gr.HTML(
170
  """
171
+ <div style="text-align:center; max-width:900px; margin:0 auto;">
172
+ <h1 style="font-size:2.2em; margin-bottom:6px;">🎬 Image to Video Generator with Ovi</h1>
173
+ <p style="color:#777; margin:0 0 8px;">Transform your static images into dynamic videos with synchronized audio using AI!</p>
174
+ <div class="notice">
175
+ <b>Heads up:</b> This is a paid app that uses <b>your</b> inference provider credits when you run generations.
176
+ Free users get <b>$0.10 in included credits</b>. <b>PRO users</b> get <b>$2 in included credits</b>
177
+ and can continue using beyond that (with billing).
178
+ <a href='http://huggingface.co/subscribe/pro?source=ovi' target='_blank' style='color:#fff; text-decoration:underline; font-weight:bold;'>Subscribe to PRO</a>
179
+ for more credits. Please sign in with your Hugging Face account to continue.
180
+ </div>
181
+ <p style="font-size: 0.9em; color: #999; margin-top: 10px;">
182
+ Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color:#667eea; text-decoration:underline;">anycoder</a>
183
+ </p>
184
  </div>
185
  """
186
  )
187
 
188
  gr.Markdown(
189
  """
 
 
 
 
190
  Powered by Ovi: Twin Backbone Cross-Modal Fusion for Audio-Video Generation via [HuggingFace Inference Providers](https://huggingface.co/docs/huggingface_hub/guides/inference).
191
  """
192
  )
193
 
 
 
 
 
 
 
 
 
194
  # Add login button - required for OAuth
195
+ login_btn = gr.LoginButton("Sign in with Hugging Face")
196
 
197
  gr.HTML(
198
  """
 
228
  with gr.Column(scale=1):
229
  image_input = gr.Image(
230
  label="📸 Upload Image",
231
+ type="pil",
 
232
  height=400,
233
  )
234
 
235
  prompt_input = gr.Textbox(
236
  label="✍️ Text Prompt",
237
  lines=3,
238
+ placeholder="Describe the motion you want to see in the video..."
239
  )
240
 
241
+ with gr.Row():
242
+ generate_btn = gr.Button(
243
+ "🎬 Generate Video",
244
+ variant="primary",
245
+ scale=2,
246
+ )
247
+
248
+ clear_btn = gr.ClearButton(
249
+ value="🗑️ Clear",
250
+ scale=1,
251
+ )
252
 
253
+ status = gr.Textbox(
254
+ label="Status",
255
+ interactive=False,
256
+ visible=True,
257
+ elem_classes=["status-box"]
258
  )
259
 
260
  gr.Examples(
 
273
  label="🎥 Generated Video",
274
  height=400,
275
  autoplay=True,
276
+ show_download_button=True,
277
  )
278
 
279
  gr.Markdown(
 
297
  # Event handlers with authentication
298
  generate_btn.click(
299
  fn=generate_video_with_auth,
300
+ inputs=[image_input, prompt_input, login_btn],
301
+ outputs=[video_output, status],
302
+ show_progress="full",
303
  queue=False,
304
  api_name=False,
305
  show_api=False,
306
  )
307
 
308
  clear_btn.click(
309
+ fn=clear_inputs,
310
+ inputs=[],
311
+ outputs=[image_input, prompt_input, video_output, status],
312
  queue=False,
313
  )
314
 
 
341
 
342
  # Launch the app
343
  if __name__ == "__main__":
344
+ try:
345
+ cleanup_temp_files()
346
+ if os.path.exists("gradio_cached_examples"):
347
+ shutil.rmtree("gradio_cached_examples", ignore_errors=True)
348
+ except Exception as e:
349
+ print(f"Initial cleanup error: {e}")
350
+
351
+ demo.queue(status_update_rate="auto", api_open=False, default_concurrency_limit=None)
352
  demo.launch(
353
  show_api=False,
354
+ share=False,
355
+ show_error=True,
356
  enable_monitoring=False,
357
  quiet=True,
358
+ ssr_mode=True
359
  )