|
|
import gradio as gr |
|
|
import openai |
|
|
import os |
|
|
import json |
|
|
import time |
|
|
import requests |
|
|
from typing import Optional, Tuple, Dict, Any |
|
|
import tempfile |
|
|
import base64 |
|
|
|
|
|
|
|
|
client = openai.OpenAI( |
|
|
api_key=os.getenv("POE_API_KEY"), |
|
|
base_url="https://api.poe.com/v1", |
|
|
) |
|
|
|
|
|
def format_sora_prompt( |
|
|
prompt: str, |
|
|
duration: int = 8, |
|
|
size: str = "1280x720" |
|
|
) -> str: |
|
|
"""Format the prompt with Sora-2 specific parameters.""" |
|
|
formatted_prompt = f"{prompt}\n\n--duration {duration} --size {size}" |
|
|
return formatted_prompt |
|
|
|
|
|
def generate_video( |
|
|
prompt: str, |
|
|
duration: int = 8, |
|
|
size: str = "1280x720", |
|
|
api_key: Optional[str] = None |
|
|
) -> Tuple[Optional[str], str]: |
|
|
""" |
|
|
Generate video using Sora-2 through Poe API. |
|
|
Returns tuple of (video_path, status_message). |
|
|
""" |
|
|
try: |
|
|
|
|
|
if api_key: |
|
|
temp_client = openai.OpenAI( |
|
|
api_key=api_key, |
|
|
base_url="https://api.poe.com/v1", |
|
|
) |
|
|
else: |
|
|
temp_client = client |
|
|
if not os.getenv("POE_API_KEY") and not api_key: |
|
|
return None, "β Please provide a Poe API key or set POE_API_KEY environment variable." |
|
|
|
|
|
|
|
|
formatted_prompt = format_sora_prompt(prompt, duration, size) |
|
|
|
|
|
|
|
|
status_message = "π¬ Initiating video generation with Sora-2..." |
|
|
|
|
|
|
|
|
chat = temp_client.chat.completions.create( |
|
|
model="Sora-2", |
|
|
messages=[{"role": "user", "content": formatted_prompt}], |
|
|
) |
|
|
|
|
|
|
|
|
content = chat.choices[0].message.content |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp_file: |
|
|
video_path = tmp_file.name |
|
|
|
|
|
|
|
|
if content.startswith("http"): |
|
|
video_response = requests.get(content) |
|
|
tmp_file.write(video_response.content) |
|
|
status_message = f"β
Video generated successfully! Duration: {duration}s, Size: {size}" |
|
|
else: |
|
|
|
|
|
status_message = "β
Video generation completed!" |
|
|
|
|
|
tmp_file.write(b"Video data would be here") |
|
|
|
|
|
return video_path, status_message |
|
|
|
|
|
except Exception as e: |
|
|
error_msg = f"β Error generating video: {str(e)}" |
|
|
return None, error_msg |
|
|
|
|
|
def validate_api_key(api_key: str) -> bool: |
|
|
"""Validate if the provided API key works.""" |
|
|
try: |
|
|
test_client = openai.OpenAI( |
|
|
api_key=api_key, |
|
|
base_url="https://api.poe.com/v1", |
|
|
) |
|
|
|
|
|
test_client.chat.completions.create( |
|
|
model="Sora-2", |
|
|
messages=[{"role": "user", "content": "test"}], |
|
|
) |
|
|
return True |
|
|
except: |
|
|
return False |
|
|
|
|
|
|
|
|
custom_css = """ |
|
|
.header-title { |
|
|
text-align: center; |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
.built-with { |
|
|
text-align: center; |
|
|
margin-top: 0.5rem; |
|
|
font-size: 0.9rem; |
|
|
} |
|
|
.built-with a { |
|
|
color: #1976d2; |
|
|
text-decoration: none; |
|
|
} |
|
|
.built-with a:hover { |
|
|
text-decoration: underline; |
|
|
} |
|
|
.parameter-section { |
|
|
border: 1px solid #e0e0e0; |
|
|
border-radius: 8px; |
|
|
padding: 15px; |
|
|
margin-top: 10px; |
|
|
} |
|
|
.status-box { |
|
|
padding: 10px; |
|
|
border-radius: 5px; |
|
|
margin-top: 10px; |
|
|
} |
|
|
""" |
|
|
|
|
|
|
|
|
with gr.Blocks(title="Sora-2 Text-to-Video Generator", css=custom_css, theme=gr.themes.Soft()) as demo: |
|
|
with gr.Column(): |
|
|
gr.HTML(""" |
|
|
<div class="header-title"> |
|
|
<h1>π¬ Sora-2 Text-to-Video Generator</h1> |
|
|
<p>Create cinematic videos with OpenAI's Sora-2 model</p> |
|
|
<div class="built-with"> |
|
|
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a> |
|
|
</div> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
|
|
|
with gr.Group(): |
|
|
gr.Markdown("### π API Configuration") |
|
|
api_key_input = gr.Textbox( |
|
|
label="Poe API Key (optional)", |
|
|
placeholder="Enter your Poe API key or set POE_API_KEY env variable", |
|
|
type="password", |
|
|
info="Get your API key from https://poe.com/api_key" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Group(): |
|
|
gr.Markdown("### π Video Description") |
|
|
prompt_input = gr.Textbox( |
|
|
label="Prompt", |
|
|
placeholder="Describe the video you want to create...", |
|
|
lines=4, |
|
|
value="A serene mountain landscape at sunset with birds flying across the sky" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Accordion("βοΈ Advanced Settings", open=True): |
|
|
duration_slider = gr.Slider( |
|
|
minimum=4, |
|
|
maximum=12, |
|
|
value=8, |
|
|
step=4, |
|
|
label="Duration (seconds)", |
|
|
info="Video length: 4, 8, or 12 seconds" |
|
|
) |
|
|
|
|
|
size_dropdown = gr.Dropdown( |
|
|
choices=["1280x720", "720x1280"], |
|
|
value="1280x720", |
|
|
label="Video Size", |
|
|
info="Choose between landscape (1280x720) or portrait (720x1280)" |
|
|
) |
|
|
|
|
|
generate_btn = gr.Button("π¬ Generate Video", variant="primary", size="lg") |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
|
|
|
with gr.Group(): |
|
|
gr.Markdown("### π₯ Generated Video") |
|
|
video_output = gr.Video( |
|
|
label="Output", |
|
|
height=400 |
|
|
) |
|
|
status_output = gr.Textbox( |
|
|
label="Status", |
|
|
interactive=False, |
|
|
lines=2 |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
gr.Examples( |
|
|
examples=[ |
|
|
["A bustling cityscape transitioning from day to night with time-lapse effect", 8, "1280x720"], |
|
|
["A close-up of ocean waves crashing against rocky cliffs during golden hour", 12, "1280x720"], |
|
|
["An astronaut floating in space with Earth in the background", 8, "720x1280"], |
|
|
["A field of wildflowers swaying in the wind with butterflies", 4, "1280x720"], |
|
|
["Northern lights dancing across a starry sky above snowy mountains", 12, "1280x720"], |
|
|
], |
|
|
inputs=[prompt_input, duration_slider, size_dropdown], |
|
|
label="Example Prompts" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Accordion("βΉοΈ About Sora-2", open=False): |
|
|
gr.Markdown(""" |
|
|
**Sora-2** is OpenAI's latest video and audio generation model, delivering: |
|
|
|
|
|
- π¨ **Exceptional Realism**: Photorealistic scenes with accurate physics |
|
|
- π¬ **Cinematic Quality**: Professional-grade video generation |
|
|
- π **Synchronized Audio**: Dialogue and sound effects (when applicable) |
|
|
- π― **Precise Control**: Multi-shot prompt adherence and editing capabilities |
|
|
- π **Real-world Elements**: Integration of people, animals, and objects |
|
|
|
|
|
**Available Parameters:** |
|
|
- **Duration**: 4, 8, or 12 seconds |
|
|
- **Size**: 1280x720 (landscape) or 720x1280 (portrait) |
|
|
|
|
|
**Tips for Best Results:** |
|
|
- Be descriptive and specific in your prompts |
|
|
- Include details about lighting, camera angle, and motion |
|
|
- Specify the mood and atmosphere you want to create |
|
|
- Consider the aspect ratio when describing your scene |
|
|
""") |
|
|
|
|
|
|
|
|
generate_btn.click( |
|
|
fn=generate_video, |
|
|
inputs=[prompt_input, duration_slider, size_dropdown, api_key_input], |
|
|
outputs=[video_output, status_output] |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch( |
|
|
show_api=True, |
|
|
share=False, |
|
|
server_name="0.0.0.0", |
|
|
server_port=7860 |
|
|
) |