celinah HF Staff commited on
Commit
389b237
·
1 Parent(s): 1f0c608
Files changed (2) hide show
  1. app.py +210 -9
  2. requirements.txt +2 -1
app.py CHANGED
@@ -1,6 +1,7 @@
1
  import os
2
  import tempfile
3
 
 
4
  import gradio as gr
5
  import numpy as np
6
  import requests
@@ -9,6 +10,9 @@ from dotenv import load_dotenv
9
  from huggingface_hub import InferenceClient
10
 
11
 
 
 
 
12
  load_dotenv()
13
 
14
  MAX_SEED = np.iinfo(np.int32).max
@@ -39,14 +43,50 @@ def download_image_locally(image_url: str, local_path: str = "downloaded_image.p
39
  return local_path
40
 
41
 
42
- def login(oauth_token: gr.OAuthToken | None):
43
- global TOKEN
 
 
 
 
 
 
 
 
44
  if oauth_token and oauth_token.token:
45
- print("Received OAuth token, logging in...")
46
  TOKEN = oauth_token.token
47
  else:
48
- print("No OAuth token provided, using environment variable HF_TOKEN.")
49
- TOKEN = os.environ.get("HF_TOKEN")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
 
52
  def generate(prompt: str, seed: int = 42, width: int = 1024, height: int = 1024, num_inference_steps: int = 25):
@@ -78,6 +118,79 @@ def generate(prompt: str, seed: int = 42, width: int = 1024, height: int = 1024,
78
  return image, seed
79
 
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  examples = [
82
  "a tiny astronaut hatching from an egg on the moon",
83
  "a cat holding a sign that says hello world",
@@ -98,8 +211,14 @@ with gr.Blocks(css=css) as demo:
98
  gr.Markdown(
99
  "This Space showcases the black‑forest‑labs/FLUX.1‑dev model, served by the nebius API. Sign in with your Hugging Face account to use this API."
100
  )
101
- button = gr.LoginButton("Sign in")
102
- button.click(fn=login, inputs=[], outputs=[])
 
 
 
 
 
 
103
  with gr.Column(elem_id="col-container"):
104
  gr.Markdown(
105
  """# FLUX.1 [schnell] with fal‑ai through HF Inference Providers ⚡\nLearn more about HF Inference Providers [here](https://huggingface.co/docs/inference-providers/index)"""
@@ -115,9 +234,9 @@ with gr.Blocks(css=css) as demo:
115
  )
116
  run_button = gr.Button("Run", scale=0)
117
 
118
- result = gr.Image(label="Result", show_label=False, format="png")
119
  download_btn = gr.DownloadButton(
120
- label="Download result",
121
  visible=False,
122
  value=None,
123
  variant="primary",
@@ -164,12 +283,94 @@ with gr.Blocks(css=css) as demo:
164
  cache_examples="lazy",
165
  )
166
 
 
 
 
 
 
 
 
 
 
167
  run_button.click(
168
  fn=generate,
169
  inputs=[prompt, seed_slider, width_slider, height_slider, steps_slider],
170
  outputs=[result, seed_number],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  )
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  with gr.Accordion("Download Image from URL", open=False):
174
  image_url_input = gr.Text(label="Image URL", placeholder="Enter image URL (e.g., http://.../image.png)")
175
  filename_input = gr.Text(
 
1
  import os
2
  import tempfile
3
 
4
+ import fal_client
5
  import gradio as gr
6
  import numpy as np
7
  import requests
 
10
  from huggingface_hub import InferenceClient
11
 
12
 
13
+ FAL_KEY = os.environ.get("FAL_KEY") # Load FAL_KEY
14
+
15
+
16
  load_dotenv()
17
 
18
  MAX_SEED = np.iinfo(np.int32).max
 
43
  return local_path
44
 
45
 
46
+ def login(oauth_token: gr.OAuthToken | None, fal_key_from_ui: str | None):
47
+ """
48
+ Login to Hugging Face and FAL.
49
+
50
+ Args:
51
+ oauth_token (gr.OAuthToken | None): The OAuth token from Hugging Face.
52
+ fal_key_from_ui (str | None): The FAL key from the UI.
53
+ """
54
+ global TOKEN, FAL_KEY
55
+
56
  if oauth_token and oauth_token.token:
57
+ print("Received OAuth token, logging in for Hugging Face...")
58
  TOKEN = oauth_token.token
59
  else:
60
+ env_hf_token = os.environ.get("HF_TOKEN")
61
+ if env_hf_token:
62
+ TOKEN = env_hf_token
63
+ print("Using environment variable HF_TOKEN for Hugging Face.")
64
+ else:
65
+ print("No Hugging Face OAuth token received and HF_TOKEN environment variable not set.")
66
+
67
+ if fal_key_from_ui and fal_key_from_ui.strip():
68
+ FAL_KEY = fal_key_from_ui.strip()
69
+ elif os.environ.get("FAL_KEY"):
70
+ if FAL_KEY == os.environ.get("FAL_KEY"):
71
+ print("Using FAL_KEY from environment variable.")
72
+ else:
73
+ FAL_KEY = os.environ.get("FAL_KEY")
74
+ print("Using FAL_KEY from environment variable (UI input was blank).")
75
+ gr.Info("FAL_KEY has been set from environment variable.")
76
+
77
+ else:
78
+ print("FAL_KEY not provided in UI or environment.")
79
+ FAL_KEY = None
80
+
81
+ if not TOKEN:
82
+ gr.Warning("Hugging Face token not set. Image generation via HF Inference Providers might fail.")
83
+ else:
84
+ gr.Info("Hugging Face token is configured.")
85
+
86
+ if not FAL_KEY:
87
+ gr.Warning("FAL_KEY not set. Video generation will not work.")
88
+ else:
89
+ gr.Info("FAL_KEY is configured.")
90
 
91
 
92
  def generate(prompt: str, seed: int = 42, width: int = 1024, height: int = 1024, num_inference_steps: int = 25):
 
118
  return image, seed
119
 
120
 
121
+ def generate_video_from_image(
122
+ image_filepath: str, # This will be the path to the image from gr.Image output
123
+ video_prompt: str,
124
+ duration: str, # "5" or "10"
125
+ aspect_ratio: str, # "16:9", "9:16", "1:1"
126
+ video_negative_prompt: str,
127
+ cfg_scale_video: float,
128
+ progress=gr.Progress(track_tqdm=True),
129
+ ):
130
+ """
131
+ Generates a video from an image using fal-ai/kling-video API.
132
+ """
133
+ if not FAL_KEY:
134
+ gr.Error("FAL_KEY is not set. Cannot generate video.")
135
+ return None
136
+ if not image_filepath:
137
+ gr.Warning("No image provided to generate video from.")
138
+ return None
139
+ if not os.path.exists(image_filepath):
140
+ gr.Error(f"Image file not found at: {image_filepath}")
141
+ return None
142
+
143
+ print(f"Video generation started for image: {image_filepath}")
144
+ progress(0, desc="Preparing for video generation...")
145
+
146
+ try:
147
+ progress(0.1, desc="Uploading image...")
148
+ print("Uploading image to fal.ai storage...")
149
+ image_url = fal_client.upload_file(image_filepath)
150
+ print(f"Image uploaded, URL: {image_url}")
151
+ progress(0.3, desc="Image uploaded. Submitting video request...")
152
+
153
+ def on_queue_update(update):
154
+ if isinstance(update, fal_client.InProgress):
155
+ if update.logs:
156
+ for log in update.logs:
157
+ print(f"[fal-ai log] {log['message']}")
158
+ # Try to update progress description if logs are available
159
+ # progress(progress.current_progress_value, desc=f"Video processing: {log['message'][:50]}...")
160
+
161
+ print("Subscribing to fal-ai/kling-video/v2.1/master/image-to-video...")
162
+ api_result = fal_client.subscribe(
163
+ "fal-ai/kling-video/v2.1/master/image-to-video",
164
+ arguments={
165
+ "prompt": video_prompt,
166
+ "image_url": image_url,
167
+ "duration": duration,
168
+ "aspect_ratio": aspect_ratio,
169
+ "negative_prompt": video_negative_prompt,
170
+ "cfg_scale": cfg_scale_video,
171
+ },
172
+ with_logs=True, # Get logs
173
+ on_queue_update=on_queue_update, # Callback for logs
174
+ )
175
+
176
+ progress(0.9, desc="Video processing complete.")
177
+ video_output_url = api_result.get("video", {}).get("url")
178
+
179
+ if video_output_url:
180
+ print(f"Video generated successfully: {video_output_url}")
181
+ progress(1, desc="Video ready!")
182
+ return video_output_url
183
+ else:
184
+ print(f"Video generation failed or no URL in response. API Result: {api_result}")
185
+ gr.Error("Video generation failed or no video URL returned.")
186
+ return None
187
+
188
+ except Exception as e:
189
+ print(f"Error during video generation: {e}")
190
+ gr.Error(f"An error occurred: {str(e)}")
191
+ return None
192
+
193
+
194
  examples = [
195
  "a tiny astronaut hatching from an egg on the moon",
196
  "a cat holding a sign that says hello world",
 
211
  gr.Markdown(
212
  "This Space showcases the black‑forest‑labs/FLUX.1‑dev model, served by the nebius API. Sign in with your Hugging Face account to use this API."
213
  )
214
+ hf_login_button = gr.LoginButton("Sign in")
215
+ fal_key_input = gr.Textbox(
216
+ label="FAL_KEY",
217
+ placeholder="Enter your FAL API Key here",
218
+ type="password",
219
+ value=FAL_KEY if FAL_KEY else "", # Pre-fill if loaded from env
220
+ )
221
+ hf_login_button.click(fn=login, inputs=[hf_login_button, fal_key_input], outputs=None)
222
  with gr.Column(elem_id="col-container"):
223
  gr.Markdown(
224
  """# FLUX.1 [schnell] with fal‑ai through HF Inference Providers ⚡\nLearn more about HF Inference Providers [here](https://huggingface.co/docs/inference-providers/index)"""
 
234
  )
235
  run_button = gr.Button("Run", scale=0)
236
 
237
+ result = gr.Image(label="Generated Image", show_label=False, format="png", type="filepath")
238
  download_btn = gr.DownloadButton(
239
+ label="Download result image",
240
  visible=False,
241
  value=None,
242
  variant="primary",
 
283
  cache_examples="lazy",
284
  )
285
 
286
+ def update_image_outputs(image_pil, seed_val):
287
+ return {
288
+ result: image_pil,
289
+ seed_number: seed_val,
290
+ download_btn: gr.DownloadButton(value=image_pil, visible=True)
291
+ if image_pil
292
+ else gr.DownloadButton(visible=False),
293
+ }
294
+
295
  run_button.click(
296
  fn=generate,
297
  inputs=[prompt, seed_slider, width_slider, height_slider, steps_slider],
298
  outputs=[result, seed_number],
299
+ ).then(
300
+ lambda img_path, vid_accordion, vid_btn: { # Make video section interactive
301
+ vid_accordion: gr.Accordion(open=True, interactive=True),
302
+ vid_btn: gr.Button(interactive=True),
303
+ },
304
+ inputs=[result],
305
+ outputs=[],
306
+ )
307
+
308
+ video_result_output = gr.Video(label="Generated Video", show_label=False)
309
+
310
+ with gr.Accordion("Video Generation from Image", open=False, interactive=False) as video_gen_accordion:
311
+ video_prompt_input = gr.Text(
312
+ label="Prompt for Video",
313
+ placeholder="Describe the animation or changes for the video (e.g., 'camera zooms out slowly')",
314
+ value="A gentle breeze rustles the leaves, subtle camera movement.", # Default prompt
315
+ )
316
+ with gr.Row():
317
+ video_duration_input = gr.Dropdown(label="Duration (seconds)", choices=["5", "10"], value="5")
318
+ video_aspect_ratio_input = gr.Dropdown(
319
+ label="Aspect Ratio",
320
+ choices=["16:9", "9:16", "1:1"],
321
+ value="16:9", # Default from API
322
+ )
323
+ video_negative_prompt_input = gr.Text(
324
+ label="Negative Prompt for Video",
325
+ value="blur, distort, low quality", # Default from API
326
+ )
327
+ video_cfg_scale_input = gr.Slider(
328
+ label="CFG Scale for Video",
329
+ minimum=0.0,
330
+ maximum=10.0,
331
+ value=0.5,
332
+ step=0.1, # Default from API (0.5 seems low, API docs mention it, let's check if it's a typo or specific to this model)
333
+ )
334
+ generate_video_btn = gr.Button("Generate Video", interactive=False)
335
+
336
+ # Update the run_button.click().then() to target these video components
337
+ # We need to define them first, so I'm moving the .then() part of run_button here.
338
+ # This is a bit tricky with Gradio's sequential definition. Let's re-organize slightly.
339
+
340
+ # The previous run_button.click had a .then() that needs video_gen_accordion and generate_video_btn
341
+ # We'll chain it properly after these are defined.
342
+
343
+ generate_video_btn.click(
344
+ fn=generate_video_from_image,
345
+ inputs=[
346
+ result, # This is the gr.Image component, its output (filepath) will be passed
347
+ video_prompt_input,
348
+ video_duration_input,
349
+ video_aspect_ratio_input,
350
+ video_negative_prompt_input,
351
+ video_cfg_scale_input,
352
+ ],
353
+ outputs=[video_result_output],
354
  )
355
 
356
+ # Now, correctly chain the .then() for the image generation button
357
+ run_button.click(
358
+ fn=generate,
359
+ inputs=[prompt, seed_slider, width_slider, height_slider, steps_slider],
360
+ outputs=[result, seed_number],
361
+ ).then(
362
+ # This function will run after 'generate' and will update the UI
363
+ # It receives the outputs of 'generate' as its inputs.
364
+ # We use `result` (the gr.Image component's output which is a filepath)
365
+ # to enable the video section.
366
+ lambda image_filepath: { # image_filepath will be the path from the `result` gr.Image
367
+ video_gen_accordion: gr.Accordion(open=True, interactive=True if image_filepath else False),
368
+ generate_video_btn: gr.Button(interactive=True if image_filepath else False),
369
+ download_btn: gr.DownloadButton(value=image_filepath, visible=True if image_filepath else False),
370
+ },
371
+ inputs=[result], # Input to this lambda is the output of `result` (gr.Image)
372
+ outputs=[video_gen_accordion, generate_video_btn, download_btn],
373
+ )
374
  with gr.Accordion("Download Image from URL", open=False):
375
  image_url_input = gr.Text(label="Image URL", placeholder="Enter image URL (e.g., http://.../image.png)")
376
  filename_input = gr.Text(
requirements.txt CHANGED
@@ -1,3 +1,4 @@
1
  huggingface-hub
2
  numpy
3
- python-dotenv
 
 
1
  huggingface-hub
2
  numpy
3
+ python-dotenv
4
+ fal-client