| | 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() |