| import gradio as gr |
| import requests |
| import os |
| import time |
| import json |
| import logging |
|
|
| |
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger(__name__) |
|
|
| |
| API_KEY = os.environ.get("Key", "") |
| if not API_KEY: |
| logger.error("❌ No API key found in environment variable 'Key'") |
| else: |
| logger.info(f"✅ API key loaded (length: {len(API_KEY)})") |
|
|
| def generate_video(prompt_text, image_url, model_id, progress=gr.Progress()): |
| """Generate video using direct API calls (curl approach)""" |
| |
| if not API_KEY: |
| yield "❌ API key not configured. Please set the 'Key' secret.", None |
| return |
| |
| try: |
| progress(0, desc="Starting request...") |
| |
| |
| url = "https://ark.ap-southeast.bytepluses.com/api/v3/contents/generations/tasks" |
| |
| headers = { |
| "Content-Type": "application/json", |
| "Authorization": f"Bearer {API_KEY}" |
| } |
| |
| |
| data = { |
| "model": model_id, |
| "content": [ |
| { |
| "type": "text", |
| "text": prompt_text |
| }, |
| { |
| "type": "image_url", |
| "image_url": { |
| "url": image_url |
| } |
| } |
| ] |
| } |
| |
| logger.info(f"📤 Sending request to: {url}") |
| logger.info(f"📝 Model: {model_id}") |
| |
| |
| response = requests.post(url, headers=headers, json=data) |
| |
| logger.info(f"📥 Response status: {response.status_code}") |
| |
| if response.status_code != 200: |
| error_msg = f"Error {response.status_code}: {response.text}" |
| logger.error(f"❌ {error_msg}") |
| yield error_msg, None |
| return |
| |
| result = response.json() |
| task_id = result.get('id') |
| |
| if not task_id: |
| yield "❌ No task ID in response", None |
| return |
| |
| logger.info(f"✅ Task created: {task_id}") |
| yield f"Task created: {task_id}", None |
| |
| |
| progress(0.2, desc="Waiting for generation...") |
| |
| |
| status_url = f"https://ark.ap-southeast.bytepluses.com/api/v3/contents/generations/tasks/{task_id}" |
| |
| max_attempts = 60 |
| attempts = 0 |
| |
| while attempts < max_attempts: |
| progress(0.2 + (attempts / max_attempts) * 0.7, |
| desc=f"Polling... ({attempts + 1}/{max_attempts})") |
| |
| status_response = requests.get(status_url, headers=headers) |
| |
| if status_response.status_code != 200: |
| yield f"❌ Error checking status: {status_response.text}", None |
| return |
| |
| status_result = status_response.json() |
| status = status_result.get('status') |
| |
| logger.info(f"Status: {status}") |
| |
| if status == "succeeded": |
| progress(1.0, desc="Complete!") |
| |
| |
| video_url = None |
| if 'output' in status_result and status_result['output']: |
| if isinstance(status_result['output'], list) and len(status_result['output']) > 0: |
| video_url = status_result['output'][0].get('video_url') |
| elif isinstance(status_result['output'], dict): |
| video_url = status_result['output'].get('video_url') |
| |
| if video_url: |
| yield "✅ Video generated successfully!", video_url |
| else: |
| yield "✅ Task completed (no video URL in response)", None |
| return |
| |
| elif status == "failed": |
| error = status_result.get('error', 'Unknown error') |
| yield f"❌ Task failed: {error}", None |
| return |
| else: |
| yield f"⏳ Status: {status}...", None |
| time.sleep(1) |
| attempts += 1 |
| |
| yield "⏰ Timeout: Task took too long", None |
| |
| except Exception as e: |
| logger.error(f"❌ Exception: {str(e)}") |
| yield f"❌ Error: {str(e)}", None |
|
|
| |
| with gr.Blocks(title="BytePlus Video Generator", theme=gr.themes.Soft()) as demo: |
| |
| gr.Markdown(""" |
| # 🎥 BytePlus Video Generator |
| |
| Using direct API calls (curl approach) |
| """) |
| |
| |
| if API_KEY: |
| gr.Markdown("✅ **API Key:** Configured") |
| else: |
| gr.Markdown("❌ **API Key:** Not configured - please add 'Key' secret") |
| |
| with gr.Row(): |
| with gr.Column(): |
| model_id = gr.Textbox( |
| label="Model ID", |
| value="seedance-1-5-pro-251215" |
| ) |
| |
| image_url = gr.Textbox( |
| label="Image URL", |
| value="https://ark-doc.tos-ap-southeast-1.bytepluses.com/seepro_i2v%20.png" |
| ) |
| |
| prompt = gr.Textbox( |
| label="Prompt", |
| lines=4, |
| value="At breakneck speed, drones thread through intricate obstacles or stunning natural wonders, delivering an immersive, heart-pounding flying experience. --duration 5 --camerafixed false" |
| ) |
| |
| generate_btn = gr.Button("🚀 Generate", variant="primary") |
| |
| with gr.Column(): |
| status = gr.Textbox(label="Status", lines=3) |
| video = gr.Video(label="Generated Video") |
| video_url = gr.Textbox(label="Video URL") |
| |
| |
| gr.Markdown("---") |
| with gr.Row(): |
| gr.Button("🌄 Nature").click( |
| fn=lambda: "Aerial drone shot over mountains at sunrise, cinematic --duration 5 --camerafixed false", |
| outputs=prompt |
| ) |
| gr.Button("🏙️ City").click( |
| fn=lambda: "Fast drone racing through futuristic city streets --duration 5 --camerafixed false", |
| outputs=prompt |
| ) |
| gr.Button("🌊 Ocean").click( |
| fn=lambda: "Drone following waves at golden hour --duration 5 --camerafixed false", |
| outputs=prompt |
| ) |
| |
| |
| generate_btn.click( |
| fn=generate_video, |
| inputs=[prompt, image_url, model_id], |
| outputs=[status, video] |
| ).then( |
| fn=lambda v: v if v else "", |
| inputs=[video], |
| outputs=[video_url] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch(server_name="0.0.0.0") |