| import gradio as gr |
| import os |
| import time |
| import json |
| from PIL import Image |
| import logging |
|
|
| |
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger(__name__) |
|
|
| |
| try: |
| import byteplussdkcore |
| from byteplussdkarkruntime import Ark |
| SDK_AVAILABLE = True |
| logger.info("✅ BytePlus SDK loaded successfully") |
| except ImportError as e: |
| SDK_AVAILABLE = False |
| logger.warning(f"⚠️ BytePlus SDK not available: {e}") |
|
|
| def initialize_sdk_config(): |
| """Initialize SDK configuration as per docs""" |
| try: |
| configuration = byteplussdkcore.Configuration() |
| configuration.client_side_validation = True |
| configuration.schema = "http" |
| configuration.debug = False |
| configuration.logger_file = "sdk.log" |
| byteplussdkcore.Configuration.set_default(configuration) |
| return True |
| except Exception as e: |
| logger.error(f"SDK config error: {e}") |
| return False |
|
|
| def generate_video(api_key, prompt_text, image_url, model_id, progress=gr.Progress()): |
| """Generate video using the BytePlus SDK""" |
| |
| if not api_key or api_key == "key": |
| yield "⚠️ Please enter your actual BytePlus API key (the 'key' is just a placeholder)", None |
| return |
| |
| if not SDK_AVAILABLE: |
| yield "❌ BytePlus SDK not available. Please check installation of byteplus-python-sdk-v2", None |
| return |
| |
| try: |
| progress(0, desc="Initializing SDK...") |
| |
| initialize_sdk_config() |
| |
| |
| os.environ["ARK_API_KEY"] = api_key |
| |
| |
| client = Ark( |
| base_url="https://ark.ap-southeast.bytepluses.com/api/v3", |
| api_key=os.environ.get("ARK_API_KEY"), |
| ) |
| |
| progress(0.1, desc="Creating video generation request...") |
| logger.info("🚀 Creating video generation request...") |
| |
| |
| create_result = client.content_generation.tasks.create( |
| model=model_id, |
| content=[ |
| { |
| "type": "text", |
| "text": prompt_text |
| }, |
| { |
| "type": "image_url", |
| "image_url": { |
| "url": image_url |
| } |
| } |
| ] |
| ) |
| |
| task_id = create_result.id |
| logger.info(f"📋 Task ID: {task_id}") |
| |
| yield f"⏳ Task created with ID: {task_id}. Waiting for completion...", None |
| |
| |
| max_attempts = 60 |
| attempts = 0 |
| |
| while attempts < max_attempts: |
| progress(0.1 + (attempts / max_attempts) * 0.8, |
| desc=f"Polling status: {attempts + 1}/{max_attempts}") |
| |
| get_result = client.content_generation.tasks.get(task_id=task_id) |
| status = get_result.status |
| |
| if status == "succeeded": |
| progress(1.0, desc="Complete!") |
| logger.info("✅ Task succeeded!") |
| |
| video_url = None |
| if hasattr(get_result, 'output') and get_result.output: |
| if isinstance(get_result.output, list) and len(get_result.output) > 0: |
| video_url = get_result.output[0].get('video_url') |
| elif hasattr(get_result.output, 'video_url'): |
| video_url = get_result.output.video_url |
| elif isinstance(get_result.output, dict): |
| video_url = get_result.output.get('video_url') |
| |
| if video_url: |
| yield f"✅ Video generated successfully!", video_url |
| else: |
| |
| result_str = str(get_result) |
| yield f"✅ Task completed. Response: {result_str[:500]}...", None |
| break |
| |
| elif status == "failed": |
| error_msg = get_result.error if hasattr(get_result, 'error') else "Unknown error" |
| yield f"❌ Task failed: {error_msg}", None |
| break |
| else: |
| yield f"⏳ Current status: {status}, waiting... ({attempts + 1}/{max_attempts})", None |
| time.sleep(1) |
| attempts += 1 |
| |
| if attempts >= max_attempts: |
| yield "⏰ Timeout: Task took too long to complete. Please try again.", None |
| |
| except Exception as e: |
| logger.error(f"Error: {e}") |
| yield f"❌ Error: {str(e)}", None |
|
|
| |
| custom_css = """ |
| .gradio-container { |
| max-width: 1200px !important; |
| margin: auto !important; |
| } |
| .video-container { |
| border-radius: 8px; |
| overflow: hidden; |
| } |
| .status-box { |
| background: #f5f5f5; |
| border-radius: 8px; |
| padding: 10px; |
| margin: 10px 0; |
| } |
| """ |
|
|
| |
| with gr.Blocks( |
| title="BytePlus Video Generator", |
| theme=gr.themes.Soft( |
| primary_hue="blue", |
| secondary_hue="purple", |
| ), |
| css=custom_css |
| ) as demo: |
| |
| gr.Markdown(""" |
| # 🎥 BytePlus Video Generator |
| Generate stunning videos from images and text prompts using BytePlus AI |
| |
| ### 📊 System Status |
| """) |
| |
| |
| with gr.Row(): |
| if SDK_AVAILABLE: |
| gr.Markdown("✅ **SDK Status:** Connected to BytePlus SDK") |
| else: |
| gr.Markdown("❌ **SDK Status:** SDK not available") |
| |
| gr.Markdown("---") |
| |
| with gr.Row(): |
| with gr.Column(scale=1, variant="panel"): |
| |
| api_key = gr.Textbox( |
| label="🔑 BytePlus API Key", |
| placeholder="Enter your BytePlus API key here", |
| type="password", |
| value="key", |
| info="Your API key will be set as ARK_API_KEY environment variable", |
| container=True, |
| scale=1 |
| ) |
| |
| |
| model_id = gr.Dropdown( |
| label="🤖 Model Selection", |
| choices=[ |
| "seedance-1-5-pro-251215", |
| "seedance-1-5-pro-251215-v2", |
| "byteplus-video-v1" |
| ], |
| value="seedance-1-5-pro-251215", |
| info="Select the model for video generation", |
| allow_custom_value=True |
| ) |
| |
| |
| with gr.Group(): |
| image_url_input = gr.Textbox( |
| label="🔗 Image URL", |
| placeholder="Enter public image URL", |
| value="https://ark-doc.tos-ap-southeast-1.bytepluses.com/seepro_i2v%20.png", |
| info="Image must be publicly accessible" |
| ) |
| |
| image_preview = gr.Image( |
| label="Image Preview", |
| type="pil", |
| height=200, |
| interactive=False |
| ) |
| |
| |
| prompt_input = gr.Textbox( |
| label="📝 Text Prompt", |
| placeholder="Describe your video...", |
| value="At breakneck speed, drones thread through intricate obstacles or stunning natural wonders, delivering an immersive, heart-pounding flying experience. --duration 5 --camerafixed false", |
| lines=4, |
| max_lines=6, |
| show_copy_button=True |
| ) |
| |
| |
| generate_btn = gr.Button( |
| "🚀 Generate Video", |
| variant="primary", |
| size="lg" |
| ) |
| |
| with gr.Column(scale=1, variant="panel"): |
| |
| status_output = gr.Textbox( |
| label="📊 Generation Status", |
| lines=5, |
| show_copy_button=True, |
| container=True |
| ) |
| |
| |
| with gr.Group(elem_classes="video-container"): |
| video_output = gr.Video( |
| label="🎬 Generated Video", |
| interactive=False, |
| show_download_button=True, |
| show_share_button=True |
| ) |
| |
| |
| video_url_output = gr.Textbox( |
| label="🔗 Video URL", |
| interactive=False, |
| show_copy_button=True |
| ) |
| |
| |
| gr.Markdown("---") |
| gr.Markdown("## 📋 Example Prompts") |
| |
| with gr.Row(): |
| with gr.Column(): |
| example1 = gr.Button("🌄 Nature Drone", size="sm") |
| gr.Markdown("Aerial shot over mountains at sunrise") |
| with gr.Column(): |
| example2 = gr.Button("🏙️ City Racing", size="sm") |
| gr.Markdown("Fast drone racing through neon city") |
| with gr.Column(): |
| example3 = gr.Button("🌊 Ocean Waves", size="sm") |
| gr.Markdown("Drone following surfers on waves") |
| |
| |
| def update_preview(url): |
| try: |
| if url and url.startswith(('http://', 'https://')): |
| from PIL import Image |
| import requests |
| from io import BytesIO |
| |
| response = requests.get(url, timeout=5) |
| img = Image.open(BytesIO(response.content)) |
| return img |
| return None |
| except: |
| return None |
| |
| |
| def set_nature(): |
| return "Aerial drone shot flying over majestic mountains at sunrise, cinematic lighting, smooth motion --duration 5 --camerafixed false" |
| |
| def set_city(): |
| return "Fast-paced drone racing through futuristic city streets with neon lights, dynamic angles, high speed --duration 5 --camerafixed false" |
| |
| def set_ocean(): |
| return "Drone following surfers riding massive waves, slow motion, dramatic ocean views, golden hour --duration 5 --camerafixed false" |
| |
| |
| image_url_input.change( |
| fn=update_preview, |
| inputs=image_url_input, |
| outputs=image_preview |
| ) |
| |
| example1.click(fn=set_nature, outputs=prompt_input) |
| example2.click(fn=set_city, outputs=prompt_input) |
| example3.click(fn=set_ocean, outputs=prompt_input) |
| |
| |
| generate_event = generate_btn.click( |
| fn=generate_video, |
| inputs=[api_key, prompt_input, image_url_input, model_id], |
| outputs=[status_output, video_output], |
| show_progress='full' |
| ) |
| |
| |
| generate_event.then( |
| fn=lambda url: url if url else "No URL available", |
| inputs=[video_output], |
| outputs=[video_url_output] |
| ) |
| |
| |
| clear_btn = gr.Button("🗑️ Clear All", variant="secondary", size="sm") |
| clear_btn.click( |
| fn=lambda: ( |
| "https://ark-doc.tos-ap-southeast-1.bytepluses.com/seepro_i2v%20.png", |
| "Enter your prompt here...", |
| "", |
| None, |
| "" |
| ), |
| outputs=[image_url_input, prompt_input, status_output, video_output, video_url_output] |
| ) |
| |
| |
| gr.Markdown("---") |
| gr.Markdown(""" |
| <div style='text-align: center'> |
| <p>Powered by BytePlus SDK | Updated with Gradio 5.23.3</p> |
| <p style='font-size: 0.8em; color: gray;'>Images must be publicly accessible. Generation takes 30-60 seconds.</p> |
| </div> |
| """) |
|
|
| if __name__ == "__main__": |
| demo.launch() |