|
|
import gradio as gr |
|
|
import os |
|
|
import tempfile |
|
|
import shutil |
|
|
from typing import Optional, Union |
|
|
from pathlib import Path |
|
|
from huggingface_hub import InferenceClient |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cleanup_temp_files(): |
|
|
try: |
|
|
temp_dir = tempfile.gettempdir() |
|
|
for file_path in Path(temp_dir).glob("*.mp4"): |
|
|
try: |
|
|
import time |
|
|
if file_path.stat().st_mtime < (time.time() - 300): |
|
|
file_path.unlink(missing_ok=True) |
|
|
except Exception: |
|
|
pass |
|
|
except Exception as e: |
|
|
print(f"Cleanup error: {e}") |
|
|
|
|
|
def _client_from_token(token: Optional[str]) -> InferenceClient: |
|
|
if not token: |
|
|
raise gr.Error("Please sign in first. This app requires your Hugging Face login.") |
|
|
|
|
|
return InferenceClient( |
|
|
provider="fal-ai", |
|
|
api_key=token, |
|
|
) |
|
|
|
|
|
def _save_bytes_as_temp_mp4(data: bytes) -> str: |
|
|
temp_file = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) |
|
|
try: |
|
|
temp_file.write(data) |
|
|
temp_file.flush() |
|
|
return temp_file.name |
|
|
finally: |
|
|
temp_file.close() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_video( |
|
|
prompt: str, |
|
|
token: gr.OAuthToken | None, |
|
|
duration: int = 8, |
|
|
size: str = "1280x720", |
|
|
*_ |
|
|
) -> Optional[str]: |
|
|
if token is None or not getattr(token, "token", None): |
|
|
raise gr.Error("Sign in with Hugging Face to continue. This app uses your inference provider credits.") |
|
|
if not prompt or not prompt.strip(): |
|
|
return None |
|
|
|
|
|
cleanup_temp_files() |
|
|
try: |
|
|
client = _client_from_token(token.token) |
|
|
|
|
|
model_id = "akhaliq/sora-2" |
|
|
try: |
|
|
video_bytes = client.text_to_video(prompt, model=model_id) |
|
|
except Exception as e: |
|
|
|
|
|
import requests |
|
|
if isinstance(e, requests.HTTPError) and getattr(e.response, "status_code", None) == 403: |
|
|
raise gr.Error( |
|
|
"Access denied by provider (403). Make sure your HF account has credits/permission " |
|
|
f"for provider 'fal-ai' and model '{model_id}'." |
|
|
) |
|
|
raise |
|
|
return _save_bytes_as_temp_mp4(video_bytes) |
|
|
except gr.Error: |
|
|
raise |
|
|
except Exception: |
|
|
raise gr.Error("Generation failed. If this keeps happening, check your provider quota or try again later.") |
|
|
|
|
|
def generate_video_from_image( |
|
|
image: Union[str, bytes, None], |
|
|
prompt: str, |
|
|
token: gr.OAuthToken | None, |
|
|
*_ |
|
|
) -> Optional[str]: |
|
|
if token is None or not getattr(token, "token", None): |
|
|
raise gr.Error("Sign in with Hugging Face to continue. This app uses your inference provider credits.") |
|
|
if not image or not prompt or not prompt.strip(): |
|
|
return None |
|
|
|
|
|
cleanup_temp_files() |
|
|
try: |
|
|
|
|
|
if isinstance(image, str): |
|
|
with open(image, "rb") as f: |
|
|
input_image = f.read() |
|
|
elif isinstance(image, (bytes, bytearray)): |
|
|
input_image = image |
|
|
else: |
|
|
return None |
|
|
|
|
|
client = _client_from_token(token.token) |
|
|
model_id = "akhaliq/sora-2-image-to-video" |
|
|
try: |
|
|
video_bytes = client.image_to_video( |
|
|
input_image, |
|
|
prompt=prompt, |
|
|
model=model_id, |
|
|
) |
|
|
except Exception as e: |
|
|
import requests |
|
|
if isinstance(e, requests.HTTPError) and getattr(e.response, "status_code", None) == 403: |
|
|
raise gr.Error( |
|
|
"Access denied by provider (403). Make sure your HF account has credits/permission " |
|
|
f"for provider 'fal-ai' and model '{model_id}'." |
|
|
) |
|
|
raise |
|
|
return _save_bytes_as_temp_mp4(video_bytes) |
|
|
except gr.Error: |
|
|
raise |
|
|
except Exception: |
|
|
raise gr.Error("Generation failed. If this keeps happening, check your provider quota or try again later.") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_ui(): |
|
|
css = ''' |
|
|
.logo-dark{display: none} |
|
|
.dark .logo-dark{display: block !important} |
|
|
.dark .logo-light{display: none} |
|
|
#sub_title{margin-top: -20px !important} |
|
|
.notice { |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
color: white; |
|
|
padding: 14px 16px; |
|
|
border-radius: 12px; |
|
|
margin: 18px auto 6px; |
|
|
max-width: 860px; |
|
|
text-align: center; |
|
|
font-size: 0.98rem; |
|
|
} |
|
|
''' |
|
|
|
|
|
with gr.Blocks(title="Sora-2 (uses your provider credits)", theme=gr.themes.Soft(), css=css) as demo: |
|
|
gr.HTML(""" |
|
|
<div style="text-align:center; max-width:900px; margin:0 auto;"> |
|
|
<h1 style="font-size:2.2em; margin-bottom:6px;">🎬 Sora-2</h1> |
|
|
<p style="color:#777; margin:0 0 8px;">Generate videos via the Hugging Face Inference API (provider: fal-ai)</p> |
|
|
<div class="notice"> |
|
|
<b>Heads up:</b> This is a paid app that uses <b>your</b> inference provider credits when you run generations. |
|
|
Free users get <b>$0.10 in included credits</b>. <b>PRO users</b> get <b>$2 in included credits</b> |
|
|
and can continue using beyond that (with billing). |
|
|
<a href='http://huggingface.co/subscribe/pro?source=sora_2' target='_blank' style='color:#fff; text-decoration:underline; font-weight:bold;'>Subscribe to PRO</a> |
|
|
for more credits. Please sign in with your Hugging Face account to continue. |
|
|
<br><a href='https://huggingface.co/settings/inference-providers/overview' target='_blank' style='color:#fff; text-decoration:underline; font-weight:bold;'>Check your billing usage here</a> |
|
|
</div> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
gr.HTML( |
|
|
""" |
|
|
<p style="text-align: center; font-size: 0.9em; color: #999; margin-top: 10px;"> |
|
|
Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color:#667eea; text-decoration:underline;">anycoder</a> |
|
|
</p> |
|
|
""" |
|
|
) |
|
|
|
|
|
login_btn = gr.LoginButton("Sign in with Hugging Face") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
prompt_input = gr.Textbox( |
|
|
label="Enter your prompt", |
|
|
placeholder="Describe the video you want to create…", |
|
|
lines=4, |
|
|
elem_id="prompt-text-input" |
|
|
) |
|
|
generate_btn = gr.Button("🎥 Generate Video", variant="primary") |
|
|
with gr.Column(scale=1): |
|
|
video_output = gr.Video( |
|
|
label="Generated Video", |
|
|
height=400, |
|
|
interactive=False, |
|
|
show_download_button=True, |
|
|
elem_id="text-to-video" |
|
|
) |
|
|
|
|
|
|
|
|
generate_btn.click( |
|
|
fn=generate_video, |
|
|
inputs=[prompt_input, login_btn], |
|
|
outputs=[video_output], |
|
|
) |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div style="text-align:center; margin: 34px 0 10px;"> |
|
|
<h3 style="margin-bottom:6px;">🖼️ ➜ 🎬 Image → Video (beta)</h3> |
|
|
<p style="color:#666; margin:0;">Turn a single image into a short video with a guiding prompt.</p> |
|
|
</div> |
|
|
""") |
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
image_input = gr.Image(label="Upload an image", type="filepath") |
|
|
img_prompt_input = gr.Textbox( |
|
|
label="Describe how the scene should evolve", |
|
|
placeholder="e.g., The cat starts to dance and spins playfully", |
|
|
lines=3, |
|
|
elem_id="img-prompt-text-input" |
|
|
) |
|
|
generate_img_btn = gr.Button("🎥 Generate from Image", variant="primary") |
|
|
with gr.Column(scale=1): |
|
|
video_output_img = gr.Video( |
|
|
label="Generated Video (from Image)", |
|
|
height=400, |
|
|
interactive=False, |
|
|
show_download_button=True, |
|
|
elem_id="image-to-video" |
|
|
) |
|
|
|
|
|
|
|
|
generate_img_btn.click( |
|
|
fn=generate_video_from_image, |
|
|
inputs=[image_input, img_prompt_input, login_btn], |
|
|
outputs=[video_output_img], |
|
|
) |
|
|
|
|
|
|
|
|
gr.Examples( |
|
|
examples=[["A majestic golden eagle soaring through a vibrant sunset sky"]], |
|
|
inputs=prompt_input |
|
|
) |
|
|
|
|
|
return demo |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
try: |
|
|
cleanup_temp_files() |
|
|
if os.path.exists("gradio_cached_examples"): |
|
|
shutil.rmtree("gradio_cached_examples", ignore_errors=True) |
|
|
except Exception as e: |
|
|
print(f"Initial cleanup error: {e}") |
|
|
|
|
|
app = create_ui() |
|
|
app.queue(status_update_rate="auto", api_open=False, default_concurrency_limit=None) |
|
|
app.launch(show_api=False, enable_monitoring=False, quiet=True, ssr_mode=True) |