akhaliq HF Staff commited on
Commit
223a766
Β·
verified Β·
1 Parent(s): dd2a94a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +81 -148
app.py CHANGED
@@ -6,21 +6,13 @@ from typing import Optional, Union
6
  from huggingface_hub import InferenceClient
7
  from pathlib import Path
8
 
9
- # Initialize Hugging Face Inference Client with fal-ai provider
10
- client = InferenceClient(
11
- provider="fal-ai",
12
- api_key=os.environ.get("HF_TOKEN"),
13
- bill_to="huggingface",
14
- )
15
 
16
  def cleanup_temp_files():
17
- """Clean up old temporary video files to prevent storage overflow."""
18
  try:
19
  temp_dir = tempfile.gettempdir()
20
- # Clean up old .mp4 files in temp directory
21
  for file_path in Path(temp_dir).glob("*.mp4"):
22
  try:
23
- # Remove files older than 5 minutes
24
  import time
25
  if file_path.stat().st_mtime < (time.time() - 300):
26
  file_path.unlink(missing_ok=True)
@@ -29,64 +21,59 @@ def cleanup_temp_files():
29
  except Exception as e:
30
  print(f"Cleanup error: {e}")
31
 
 
 
 
 
 
 
 
 
 
 
 
32
  def generate_video(
33
  prompt: str,
 
34
  duration: int = 8,
35
  size: str = "1280x720",
36
- api_key: Optional[str] = None
37
  ) -> Optional[str]:
38
- """Generate video using Sora-2 through Hugging Face Inference API with fal-ai provider."""
 
 
 
 
39
  cleanup_temp_files()
40
  try:
41
- if api_key:
42
- temp_client = InferenceClient(
43
- provider="fal-ai",
44
- api_key=api_key,
45
- bill_to="huggingface",
46
- )
47
- else:
48
- temp_client = client
49
- if not os.environ.get("HF_TOKEN") and not api_key:
50
- return None
51
-
52
- video_bytes = temp_client.text_to_video(
53
  prompt,
54
- model="akhaliq/sora-2",
 
55
  )
56
-
57
  temp_file = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
58
  try:
59
  temp_file.write(video_bytes)
60
  temp_file.flush()
61
- video_path = temp_file.name
62
  finally:
63
  temp_file.close()
64
-
65
- return video_path
66
  except Exception as e:
67
- return None
 
68
 
69
  def generate_video_from_image(
70
- image: Union[str, bytes],
71
  prompt: str,
72
- api_key: Optional[str] = None
73
  ) -> Optional[str]:
74
- """Generate a video from a single input image + prompt using Sora-2 image-to-video."""
75
- cleanup_temp_files()
76
- if not prompt or prompt.strip() == "":
77
  return None
78
- try:
79
- if api_key:
80
- temp_client = InferenceClient(
81
- provider="fal-ai",
82
- api_key=api_key,
83
- bill_to="huggingface",
84
- )
85
- else:
86
- temp_client = client
87
- if not os.environ.get("HF_TOKEN") and not api_key:
88
- return None
89
 
 
 
90
  if isinstance(image, str):
91
  with open(image, "rb") as f:
92
  input_image = f.read()
@@ -95,53 +82,23 @@ def generate_video_from_image(
95
  else:
96
  return None
97
 
98
- video_bytes = temp_client.image_to_video(
 
99
  input_image,
100
  prompt=prompt,
101
  model="akhaliq/sora-2-image-to-video",
102
  )
103
-
104
  temp_file = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
105
  try:
106
  temp_file.write(video_bytes)
107
  temp_file.flush()
108
- video_path = temp_file.name
109
  finally:
110
  temp_file.close()
111
-
112
- return video_path
113
  except Exception as e:
114
- return None
115
 
116
- def generate_with_auth(
117
- prompt: str,
118
- profile: gr.OAuthProfile | None
119
- ) -> Optional[str]:
120
- """Wrapper function that checks if user is logged in before generating video."""
121
- if profile is None:
122
- raise gr.Error("Click Sign in with Hugging Face button to use this app for free")
123
-
124
- if not prompt or prompt.strip() == "":
125
- return None
126
-
127
- return generate_video(
128
- prompt,
129
- duration=8,
130
- size="1280x720",
131
- api_key=None
132
- )
133
-
134
- def generate_with_auth_image(
135
- prompt: str,
136
- image_path: Optional[str],
137
- profile: gr.OAuthProfile | None
138
- ) -> Optional[str]:
139
- """Checks login status then calls image->video generator."""
140
- if profile is None:
141
- raise gr.Error("Click Sign in with Hugging Face button to use this app for free")
142
- if not image_path:
143
- return None
144
- return generate_video_from_image(image=image_path, prompt=prompt, api_key=None)
145
 
146
  def create_ui():
147
  css = '''
@@ -149,71 +106,64 @@ def create_ui():
149
  .dark .logo-dark{display: block !important}
150
  .dark .logo-light{display: none}
151
  #sub_title{margin-top: -20px !important}
152
- .mobile-notice {
153
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
154
  color: white;
155
- padding: 15px;
156
- border-radius: 10px;
157
- margin: 20px auto;
158
- max-width: 600px;
159
  text-align: center;
160
- }
161
- .mobile-notice a {
162
- color: #fff;
163
- text-decoration: underline;
164
- font-weight: bold;
165
  }
166
  '''
167
-
168
- with gr.Blocks(title="Sora-2", theme=gr.themes.Soft(), css=css) as demo:
169
  gr.HTML("""
170
- <div style="text-align: center; max-width: 800px; margin: 0 auto;">
171
- <h1 style="font-size: 2.5em; margin-bottom: 0.5em;">
172
- 🎬 Sora-2
173
- </h1>
174
- <p style="font-size: 1.1em; color: #666; margin-bottom: 20px;">Generate stunning videos using OpenAI's Sora-2 model</p>
175
- <div class="mobile-notice">
176
- πŸ“± On mobile? Use the optimized version: <a href="https://akhaliq-sora-2.hf.space" target="_blank">akhaliq-sora-2.hf.space</a>
177
  </div>
178
- <p style='color: orange;'>⚠️ You must Sign in with Hugging Face using the button to use this app.</p>
179
- <p style="font-size: 0.9em; color: #999; margin-top: 15px;">
180
- Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color: #667eea;">anycoder</a>
181
  </p>
182
  </div>
183
  """)
184
-
185
- # Add login button - required for OAuth
186
- gr.LoginButton()
187
-
188
- # Text -> Video
189
  with gr.Row():
190
  with gr.Column(scale=1):
191
  prompt_input = gr.Textbox(
192
  label="Enter your prompt",
193
- placeholder="Describe the video you want to create...",
194
  lines=4,
195
  elem_id="prompt-text-input"
196
  )
197
- generate_btn = gr.Button("πŸŽ₯ Generate Video", variant="primary", size="lg")
198
  with gr.Column(scale=1):
199
  video_output = gr.Video(
200
- label="Generated Video",
201
- height=400,
202
- interactive=False,
203
  show_download_button=True,
204
  elem_id="text-to-video"
205
  )
206
-
 
207
  generate_btn.click(
208
- fn=generate_with_auth,
209
- inputs=[prompt_input],
210
  outputs=[video_output],
211
  )
212
 
213
- # Image -> Video UI
214
  gr.HTML("""
215
- <div style="text-align: center; margin: 40px 0 10px;">
216
- <h3 style="margin-bottom: 8px;">πŸ–ΌοΈ ➜ 🎬 Image β†’ Video (beta)</h3>
217
  <p style="color:#666; margin:0;">Turn a single image into a short video with a guiding prompt.</p>
218
  </div>
219
  """)
@@ -229,32 +179,25 @@ def create_ui():
229
  generate_img_btn = gr.Button("πŸŽ₯ Generate from Image", variant="primary")
230
  with gr.Column(scale=1):
231
  video_output_img = gr.Video(
232
- label="Generated Video (from Image)",
233
- height=400,
234
- interactive=False,
235
  show_download_button=True,
236
  elem_id="image-to-video"
237
  )
238
 
239
  generate_img_btn.click(
240
- fn=generate_with_auth_image,
241
- inputs=[img_prompt_input, image_input],
242
  outputs=[video_output_img],
243
  )
244
-
245
- # Example usage guidance
246
  gr.Examples(
247
- examples=[
248
- ["A majestic golden eagle soaring through a vibrant sunset sky"],
249
- ],
250
- inputs=prompt_input,
251
- outputs=video_output,
252
- fn=generate_video,
253
- cache_examples=False,
254
- api_name=False,
255
- show_api=False,
256
  )
257
-
258
  return demo
259
 
260
  if __name__ == "__main__":
@@ -264,17 +207,7 @@ if __name__ == "__main__":
264
  shutil.rmtree("gradio_cached_examples", ignore_errors=True)
265
  except Exception as e:
266
  print(f"Initial cleanup error: {e}")
267
-
268
  app = create_ui()
269
- # Configure queue with optimized settings for OAuth-enabled app
270
- app.queue(
271
- status_update_rate="auto",
272
- api_open=False, # Disable public API access for security
273
- default_concurrency_limit=None # Allow multiple concurrent requests
274
- )
275
- app.launch(
276
- show_api=False,
277
- enable_monitoring=False,
278
- quiet=True,
279
- ssr_mode=True
280
- )
 
6
  from huggingface_hub import InferenceClient
7
  from pathlib import Path
8
 
9
+ # ---------- Utilities ----------
 
 
 
 
 
10
 
11
  def cleanup_temp_files():
 
12
  try:
13
  temp_dir = tempfile.gettempdir()
 
14
  for file_path in Path(temp_dir).glob("*.mp4"):
15
  try:
 
16
  import time
17
  if file_path.stat().st_mtime < (time.time() - 300):
18
  file_path.unlink(missing_ok=True)
 
21
  except Exception as e:
22
  print(f"Cleanup error: {e}")
23
 
24
+ def _client_from_token(token: Optional[str]) -> InferenceClient:
25
+ if not token:
26
+ raise gr.Error("Please sign in first. This app requires your Hugging Face login.")
27
+ return InferenceClient(
28
+ provider="fal-ai",
29
+ api_key=token, # OAuth token from gr.LoginButton
30
+ bill_to="huggingface", # keep if you need consolidated billing; otherwise remove
31
+ )
32
+
33
+ # ---------- Inference wrappers (NO env fallback) ----------
34
+
35
  def generate_video(
36
  prompt: str,
37
+ token: gr.OAuthToken | None,
38
  duration: int = 8,
39
  size: str = "1280x720",
 
40
  ) -> Optional[str]:
41
+ if token is None or not getattr(token, "token", None):
42
+ raise gr.Error("Sign in with Hugging Face to continue. This app uses your inference credits.")
43
+ if not prompt or not prompt.strip():
44
+ return None
45
+
46
  cleanup_temp_files()
47
  try:
48
+ client = _client_from_token(token.token)
49
+ video_bytes = client.text_to_video(
 
 
 
 
 
 
 
 
 
 
50
  prompt,
51
+ model="akhaliq/sora-2", # served by fal-ai
52
+ # You can pass provider-specific kwargs here if supported
53
  )
 
54
  temp_file = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
55
  try:
56
  temp_file.write(video_bytes)
57
  temp_file.flush()
58
+ return temp_file.name
59
  finally:
60
  temp_file.close()
 
 
61
  except Exception as e:
62
+ # Optional: surface a friendlier message
63
+ raise gr.Error("Generation failed. If this keeps happening, check your provider quota or try again.")
64
 
65
  def generate_video_from_image(
66
+ image: Union[str, bytes, None],
67
  prompt: str,
68
+ token: gr.OAuthToken | None,
69
  ) -> Optional[str]:
70
+ if token is None or not getattr(token, "token", None):
71
+ raise gr.Error("Sign in with Hugging Face to continue. This app uses your inference credits.")
72
+ if not image or not prompt or not prompt.strip():
73
  return None
 
 
 
 
 
 
 
 
 
 
 
74
 
75
+ cleanup_temp_files()
76
+ try:
77
  if isinstance(image, str):
78
  with open(image, "rb") as f:
79
  input_image = f.read()
 
82
  else:
83
  return None
84
 
85
+ client = _client_from_token(token.token)
86
+ video_bytes = client.image_to_video(
87
  input_image,
88
  prompt=prompt,
89
  model="akhaliq/sora-2-image-to-video",
90
  )
 
91
  temp_file = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
92
  try:
93
  temp_file.write(video_bytes)
94
  temp_file.flush()
95
+ return temp_file.name
96
  finally:
97
  temp_file.close()
 
 
98
  except Exception as e:
99
+ raise gr.Error("Generation failed. If this keeps happening, check your provider quota or try again.")
100
 
101
+ # ---------- UI ----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
  def create_ui():
104
  css = '''
 
106
  .dark .logo-dark{display: block !important}
107
  .dark .logo-light{display: none}
108
  #sub_title{margin-top: -20px !important}
109
+ .notice {
110
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
111
  color: white;
112
+ padding: 14px 16px;
113
+ border-radius: 12px;
114
+ margin: 18px auto 6px;
115
+ max-width: 820px;
116
  text-align: center;
117
+ font-size: 0.98rem;
 
 
 
 
118
  }
119
  '''
120
+
121
+ with gr.Blocks(title="Sora-2 (paid via your provider credits)", theme=gr.themes.Soft(), css=css) as demo:
122
  gr.HTML("""
123
+ <div style="text-align:center; max-width:860px; margin:0 auto;">
124
+ <h1 style="font-size:2.2em; margin-bottom:6px;">🎬 Sora-2</h1>
125
+ <p style="color:#777; margin:0 0 8px;">Generate videos via the Hugging Face Inference API (provider: fal-ai)</p>
126
+ <div class="notice">
127
+ <b>Heads up:</b> This app is <b>paid</b> and uses <b>your</b> inference provider credits when you run generations.
128
+ Please sign in with your Hugging Face account to continue.
 
129
  </div>
130
+ <p style="font-size: 0.9em; color: #999; margin-top: 10px;">
131
+ Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color:#fff; text-decoration:underline;">anycoder</a>
 
132
  </p>
133
  </div>
134
  """)
135
+
136
+ # Required login. Token is passed into all callbacks.
137
+ login_btn = gr.LoginButton("Sign in with Hugging Face")
138
+
 
139
  with gr.Row():
140
  with gr.Column(scale=1):
141
  prompt_input = gr.Textbox(
142
  label="Enter your prompt",
143
+ placeholder="Describe the video you want to create…",
144
  lines=4,
145
  elem_id="prompt-text-input"
146
  )
147
+ generate_btn = gr.Button("πŸŽ₯ Generate Video", variant="primary")
148
  with gr.Column(scale=1):
149
  video_output = gr.Video(
150
+ label="Generated Video",
151
+ height=400,
152
+ interactive=False,
153
  show_download_button=True,
154
  elem_id="text-to-video"
155
  )
156
+
157
+ # Note: we pass BOTH prompt and login_btn (token) to the function
158
  generate_btn.click(
159
+ fn=generate_video,
160
+ inputs=[prompt_input, login_btn],
161
  outputs=[video_output],
162
  )
163
 
 
164
  gr.HTML("""
165
+ <div style="text-align:center; margin: 34px 0 10px;">
166
+ <h3 style="margin-bottom:6px;">πŸ–ΌοΈ ➜ 🎬 Image β†’ Video (beta)</h3>
167
  <p style="color:#666; margin:0;">Turn a single image into a short video with a guiding prompt.</p>
168
  </div>
169
  """)
 
179
  generate_img_btn = gr.Button("πŸŽ₯ Generate from Image", variant="primary")
180
  with gr.Column(scale=1):
181
  video_output_img = gr.Video(
182
+ label="Generated Video (from Image)",
183
+ height=400,
184
+ interactive=False,
185
  show_download_button=True,
186
  elem_id="image-to-video"
187
  )
188
 
189
  generate_img_btn.click(
190
+ fn=generate_video_from_image,
191
+ inputs=[image_input, img_prompt_input, login_btn],
192
  outputs=[video_output_img],
193
  )
194
+
195
+ # Keep examples UI-only to avoid auto-charging on click
196
  gr.Examples(
197
+ examples=[["A majestic golden eagle soaring through a vibrant sunset sky"]],
198
+ inputs=prompt_input
 
 
 
 
 
 
 
199
  )
200
+
201
  return demo
202
 
203
  if __name__ == "__main__":
 
207
  shutil.rmtree("gradio_cached_examples", ignore_errors=True)
208
  except Exception as e:
209
  print(f"Initial cleanup error: {e}")
210
+
211
  app = create_ui()
212
+ app.queue(status_update_rate="auto", api_open=False, default_concurrency_limit=None)
213
+ app.launch(show_api=False, enable_monitoring=False, quiet=True, ssr_mode=True)