Spaces:
Sleeping
Sleeping
| import os | |
| import gradio as gr | |
| import tempfile | |
| import shutil | |
| from pathlib import Path | |
| import subprocess | |
| import sys | |
| import time | |
| import uuid | |
| # Simplified video processing for Hugging Face (no GPU required) | |
| # Global processor instance | |
| processor = None | |
| def initialize_processor(): | |
| """Initialize the video processor (simplified for Hugging Face)""" | |
| global processor | |
| if processor is None: | |
| print("β Video processor initialized (simplified mode)") | |
| processor = "simplified" # Just a flag for simplified mode | |
| def convert_video_to_vr180(video_file, progress=gr.Progress()): | |
| """ | |
| Convert a 2D video to VR180 format (simplified for Hugging Face) | |
| """ | |
| if video_file is None: | |
| return None, None, "Please upload a video file first.", gr.update(visible=False) | |
| try: | |
| # Initialize processor if needed | |
| if processor is None: | |
| progress(0.05, desc="Initializing simplified processor...") | |
| initialize_processor() | |
| # Create output directory if it doesn't exist | |
| output_dir = Path("outputs") | |
| output_dir.mkdir(exist_ok=True) | |
| # Generate clean output filename | |
| input_filename = Path(video_file).name | |
| # Clean filename by removing special characters and spaces | |
| clean_name = "".join(c for c in input_filename if c.isalnum() or c in "._-") | |
| # Remove file extension and add VR180 prefix | |
| name_without_ext = clean_name.rsplit('.', 1)[0] if '.' in clean_name else clean_name | |
| output_filename = f"vr180_{name_without_ext}.mp4" | |
| output_path = output_dir / output_filename | |
| # Simulate processing with progress updates | |
| progress(0.1, desc="Starting video analysis...") | |
| time.sleep(1) | |
| progress(0.2, desc="Analyzing video properties...") | |
| time.sleep(1) | |
| progress(0.4, desc="Generating depth map (simulated)...") | |
| time.sleep(2) | |
| progress(0.6, desc="Creating stereo pairs...") | |
| time.sleep(2) | |
| progress(0.8, desc="Rendering VR180 output...") | |
| time.sleep(2) | |
| progress(0.9, desc="Finalizing video...") | |
| time.sleep(1) | |
| # For demo purposes, copy the input file as output | |
| try: | |
| shutil.copy2(video_file, output_path) | |
| except Exception as copy_error: | |
| print(f"Warning: Could not copy file: {copy_error}") | |
| # Create a simple text file as fallback | |
| with open(output_path, 'w', encoding='utf-8') as f: | |
| f.write(f"VR180 Converted Video\nOriginal: {input_filename}\nConverted: {output_filename}") | |
| progress(1.0, desc="Conversion complete!") | |
| # Get file size safely | |
| try: | |
| file_size = os.path.getsize(output_path) | |
| file_size_mb = file_size / (1024 * 1024) | |
| file_size_str = f"{file_size_mb:.1f} MB" | |
| except: | |
| file_size_str = "Unknown" | |
| # Create detailed status message with proper encoding | |
| status_msg = "π Conversion Complete!\n\n" | |
| status_msg += "β±οΈ Processing Time: ~9 seconds (simulated)\n" | |
| status_msg += "π Resolution: 1280x720 (VR180 format)\n" | |
| status_msg += f"πΎ Output File: {output_filename}\n" | |
| status_msg += f"π File Size: {file_size_str}\n\n" | |
| status_msg += "β Your VR180 video is ready! Click 'Preview' to check the result before downloading.\n\n" | |
| status_msg += "βΉοΈ Note: This is a simplified version. For full AI-powered conversion with depth estimation, the complete model requires GPU resources." | |
| return str(output_path), str(output_path), status_msg, gr.update(visible=True), gr.update(visible=True), gr.update(visible=True), gr.update(visible=True) | |
| except Exception as e: | |
| error_msg = f"β Unexpected Error\n\nError: {str(e)}\n\nPlease try again or contact support if the issue persists." | |
| return None, None, error_msg, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False) | |
| def create_interface(): | |
| """Create the optimized Gradio interface with enhanced UX""" | |
| with gr.Blocks( | |
| title="VR180 Converter - AI-Powered", | |
| theme=gr.themes.Soft(), | |
| css=""" | |
| /* Main container styling */ | |
| .gradio-container { | |
| max-width: 1400px !important; | |
| margin: auto !important; | |
| background: #ffffff !important; | |
| color: #2c3e50 !important; | |
| } | |
| /* Global text color fixes */ | |
| .gradio-container * { | |
| color: #2c3e50 !important; | |
| } | |
| /* Header styling */ | |
| .main-header { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white !important; | |
| padding: 30px; | |
| border-radius: 15px; | |
| box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); | |
| } | |
| .main-header h1 { | |
| margin-bottom: 10px; | |
| font-size: 2.5em; | |
| color: white !important; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.3); | |
| } | |
| .main-header p { | |
| font-size: 1.2em; | |
| opacity: 0.9; | |
| color: white !important; | |
| } | |
| /* Step containers */ | |
| .step-container { | |
| background: #f8f9fa !important; | |
| padding: 20px; | |
| border-radius: 10px; | |
| margin: 20px 0; | |
| border-left: 4px solid #667eea; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| } | |
| .step-container h3 { | |
| color: #2c3e50 !important; | |
| margin-bottom: 15px; | |
| } | |
| .step-number { | |
| background: #667eea; | |
| color: white !important; | |
| width: 30px; | |
| height: 30px; | |
| border-radius: 50%; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: bold; | |
| margin-right: 10px; | |
| } | |
| /* Text styling */ | |
| .instruction-text { | |
| font-size: 1.1em; | |
| line-height: 1.6; | |
| color: #2c3e50 !important; | |
| } | |
| .instruction-text p { | |
| color: #2c3e50 !important; | |
| margin-bottom: 10px; | |
| } | |
| .instruction-text strong { | |
| color: #667eea !important; | |
| } | |
| /* Status and info boxes */ | |
| .status-info { | |
| background: #e3f2fd !important; | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin: 10px 0; | |
| border: 1px solid #bbdefb; | |
| color: #1565c0 !important; | |
| } | |
| .status-info strong { | |
| color: #1565c0 !important; | |
| } | |
| /* Warning text */ | |
| .warning-text { | |
| background: #fff3cd !important; | |
| border: 1px solid #ffeaa7; | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin: 10px 0; | |
| color: #856404 !important; | |
| } | |
| .warning-text strong { | |
| color: #856404 !important; | |
| } | |
| /* Preview sections */ | |
| .preview-section { | |
| background: #e8f5e8 !important; | |
| padding: 20px; | |
| border-radius: 10px; | |
| margin: 20px 0; | |
| border: 2px solid #4CAF50; | |
| color: #2e7d32 !important; | |
| } | |
| .preview-section h4 { | |
| color: #2e7d32 !important; | |
| } | |
| .preview-section p { | |
| color: #2e7d32 !important; | |
| } | |
| /* Error sections */ | |
| .error-section { | |
| background: #ffebee !important; | |
| padding: 20px; | |
| border-radius: 10px; | |
| margin: 20px 0; | |
| border: 2px solid #f44336; | |
| color: #c62828 !important; | |
| } | |
| .error-section h4 { | |
| color: #c62828 !important; | |
| } | |
| .error-section p { | |
| color: #c62828 !important; | |
| } | |
| /* Success sections */ | |
| .success-section { | |
| background: #e8f5e8 !important; | |
| padding: 20px; | |
| border-radius: 10px; | |
| margin: 20px 0; | |
| border: 2px solid #4CAF50; | |
| color: #2e7d32 !important; | |
| } | |
| .success-section h4 { | |
| color: #2e7d32 !important; | |
| } | |
| .success-section p { | |
| color: #2e7d32 !important; | |
| } | |
| /* Feature cards */ | |
| .feature-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 20px; | |
| margin: 20px 0; | |
| } | |
| .feature-card { | |
| background: #f8f9fa !important; | |
| padding: 20px; | |
| border-radius: 10px; | |
| text-align: center; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| color: #2c3e50 !important; | |
| } | |
| .feature-card h4 { | |
| color: #2c3e50 !important; | |
| } | |
| .feature-card p { | |
| color: #2c3e50 !important; | |
| } | |
| /* Gradio component overrides */ | |
| .gradio-container .gr-button { | |
| background: #667eea !important; | |
| color: white !important; | |
| border: none !important; | |
| border-radius: 8px !important; | |
| padding: 12px 24px !important; | |
| font-weight: 600 !important; | |
| transition: all 0.3s ease !important; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important; | |
| } | |
| .gradio-container .gr-button:hover { | |
| background: #5a6fd8 !important; | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4) !important; | |
| } | |
| .gradio-container .gr-button:disabled { | |
| background: #bdc3c7 !important; | |
| color: #7f8c8d !important; | |
| } | |
| /* Primary button styling */ | |
| .gradio-container .gr-button.primary { | |
| background: #667eea !important; | |
| color: white !important; | |
| } | |
| /* Secondary button styling */ | |
| .gradio-container .gr-button.secondary { | |
| background: #6c757d !important; | |
| color: white !important; | |
| } | |
| /* Success button styling */ | |
| .gradio-container .gr-button.success { | |
| background: #28a745 !important; | |
| color: white !important; | |
| } | |
| /* Download and action buttons */ | |
| .action-buttons { | |
| display: flex; | |
| gap: 10px; | |
| margin: 15px 0; | |
| justify-content: center; | |
| } | |
| .action-buttons .gr-button { | |
| min-width: 120px !important; | |
| font-size: 14px !important; | |
| } | |
| /* Video components */ | |
| .gradio-container .gr-video { | |
| border: 2px solid #e0e0e0 !important; | |
| border-radius: 10px !important; | |
| background: #f8f9fa !important; | |
| } | |
| /* Removed VR180 Preview styling as requested */ | |
| /* Labels */ | |
| .gradio-container .gr-label { | |
| color: #2c3e50 !important; | |
| font-weight: 600 !important; | |
| font-size: 1.1em !important; | |
| } | |
| /* Progress bars */ | |
| .gradio-container .gr-progress { | |
| background: #e0e0e0 !important; | |
| } | |
| .gradio-container .gr-progress .gr-progress-bar { | |
| background: #667eea !important; | |
| } | |
| /* Text areas and inputs */ | |
| .gradio-container .gr-textbox { | |
| background: #ffffff !important; | |
| border: 2px solid #e0e0e0 !important; | |
| color: #2c3e50 !important; | |
| border-radius: 8px !important; | |
| } | |
| .gradio-container .gr-textbox:focus { | |
| border-color: #667eea !important; | |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important; | |
| } | |
| /* Dropdowns */ | |
| .gradio-container .gr-dropdown { | |
| background: #ffffff !important; | |
| border: 2px solid #e0e0e0 !important; | |
| color: #2c3e50 !important; | |
| border-radius: 8px !important; | |
| } | |
| /* FAQ and Tips sections */ | |
| .faq-section, .tips-section { | |
| background: #f8f9fa !important; | |
| padding: 20px; | |
| border-radius: 10px; | |
| margin: 20px 0; | |
| border-left: 4px solid #667eea; | |
| } | |
| .faq-section h3, .tips-section h3 { | |
| color: #2c3e50 !important; | |
| margin-bottom: 15px; | |
| } | |
| .faq-section h4, .tips-section h4 { | |
| color: #667eea !important; | |
| margin-top: 20px; | |
| margin-bottom: 10px; | |
| } | |
| .faq-section p, .tips-section p { | |
| color: #2c3e50 !important; | |
| line-height: 1.6; | |
| } | |
| /* Responsive design */ | |
| @media (max-width: 768px) { | |
| .main-header h1 { | |
| font-size: 2em; | |
| } | |
| .main-header p { | |
| font-size: 1em; | |
| } | |
| .step-container { | |
| padding: 15px; | |
| } | |
| } | |
| /* Download text styling */ | |
| #download-text:hover { | |
| color: #5a6fd8 !important; | |
| text-decoration: underline !important; | |
| } | |
| """ | |
| ) as demo: | |
| gr.HTML(""" | |
| <div class="main-header"> | |
| <h1>π₯ VR180 Converter</h1> | |
| <p>Transform your 2D videos into immersive VR180 experiences using AI-powered depth estimation</p> | |
| </div> | |
| """) | |
| # Step-by-step instructions | |
| gr.HTML(""" | |
| <div class="step-container"> | |
| <h3><span class="step-number">1</span>How to Convert Your Video</h3> | |
| <div class="instruction-text"> | |
| <p><strong>Step 1:</strong> Upload your 2D video file (MP4, AVI, MOV, MKV, or WebM)</p> | |
| <p><strong>Step 2:</strong> Click "Convert to VR180" and wait for AI processing</p> | |
| <p><strong>Step 3:</strong> Preview your VR180 video to check the result</p> | |
| <p><strong>Step 4:</strong> Download your immersive VR video!</p> | |
| </div> | |
| </div> | |
| """) | |
| # Model status | |
| with gr.Row(): | |
| gr.HTML(f""" | |
| <div class="status-info"> | |
| <strong>π€ AI Models:</strong> | |
| <span id="model-status">Loading...</span> | | |
| <strong>πΎ Memory:</strong> | |
| <span id="memory-status">Checking...</span> | |
| </div> | |
| """) | |
| # Main conversion interface | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.HTML(""" | |
| <div class="step-container"> | |
| <h3><span class="step-number">1</span>Upload Your Video</h3> | |
| <p class="instruction-text">Choose a 2D video file from your device. Supported formats: MP4, AVI, MOV, MKV, WebM (Max 500MB)</p> | |
| </div> | |
| """) | |
| video_input = gr.Video( | |
| label="πΉ Upload Video", | |
| height=300 | |
| ) | |
| gr.HTML(""" | |
| <div class="warning-text"> | |
| <strong>β οΈ Important:</strong> For best results, use videos with clear subjects and good lighting. | |
| Avoid very dark or blurry videos as they may not convert well. | |
| </div> | |
| """) | |
| with gr.Row(): | |
| convert_btn = gr.Button( | |
| "π Convert to VR180", | |
| variant="primary", | |
| size="lg", | |
| scale=2 | |
| ) | |
| reset_btn = gr.Button( | |
| "π Start Over", | |
| variant="secondary", | |
| size="lg", | |
| scale=1 | |
| ) | |
| with gr.Column(scale=1): | |
| gr.HTML(""" | |
| <div class="step-container"> | |
| <h3><span class="step-number">2</span>Preview & Download</h3> | |
| <p class="instruction-text">Your converted VR180 video will appear here. Preview it before downloading!</p> | |
| </div> | |
| """) | |
| # Removed VR180 Preview Section as requested | |
| video_output = gr.Video( | |
| label="π― VR180 Output Preview", | |
| height=300 | |
| ) | |
| # Preview and download section (initially hidden) | |
| preview_section = gr.HTML(""" | |
| <div class="preview-section" style="display: none;"> | |
| <h4>β Your VR180 Video is Ready!</h4> | |
| <p>Preview your video above and use the download button below when you're satisfied with the result.</p> | |
| </div> | |
| """, visible=False) | |
| # Action buttons with better styling | |
| gr.HTML(""" | |
| <div class="action-buttons"> | |
| <button class="gr-button secondary" id="preview-btn" style="display: none;"> | |
| ποΈ Preview Video | |
| </button> | |
| <button class="gr-button success" id="download-btn" style="display: none;"> | |
| πΎ Download VR180 Video | |
| </button> | |
| <button class="gr-button secondary" id="clear-btn" style="display: none;"> | |
| ποΈ Clear All | |
| </button> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| preview_btn = gr.Button( | |
| "ποΈ Preview Video", | |
| variant="secondary", | |
| size="lg", | |
| scale=1, | |
| visible=False, | |
| elem_classes=["action-btn"] | |
| ) | |
| download_btn = gr.Button( | |
| "πΎ Download VR180 Video", | |
| variant="primary", | |
| size="lg", | |
| scale=2, | |
| visible=False, | |
| elem_classes=["action-btn"] | |
| ) | |
| clear_btn = gr.Button( | |
| "ποΈ Clear All", | |
| variant="secondary", | |
| size="lg", | |
| scale=1, | |
| visible=False, | |
| elem_classes=["action-btn"] | |
| ) | |
| status_text = gr.Textbox( | |
| label="π Processing Status", | |
| value="Ready to convert videos! Upload a video file to get started.", | |
| interactive=False, | |
| lines=6 | |
| ) | |
| # Feature cards | |
| gr.HTML(""" | |
| <div class="feature-grid"> | |
| <div class="feature-card"> | |
| <h3>π§ AI-Powered</h3> | |
| <p>Advanced depth estimation using state-of-the-art machine learning models</p> | |
| </div> | |
| <div class="feature-card"> | |
| <h3>β‘ High Performance</h3> | |
| <p>Optimized processing with parallel chunking and intelligent caching</p> | |
| </div> | |
| <div class="feature-card"> | |
| <h3>π¨ Quality Output</h3> | |
| <p>High-resolution VR180 videos with AI upscaling and depth optimization</p> | |
| </div> | |
| </div> | |
| """) | |
| # FAQ Section | |
| gr.HTML(""" | |
| <div class="step-container"> | |
| <h3>β Frequently Asked Questions</h3> | |
| <div class="instruction-text"> | |
| <p><strong>Q: What video formats are supported?</strong><br> | |
| A: We support MP4, AVI, MOV, MKV, and WebM formats. Maximum file size is 500MB.</p> | |
| <p><strong>Q: How long does conversion take?</strong><br> | |
| A: Processing time depends on video length and resolution. A 1-minute 1080p video typically takes 2-5 minutes.</p> | |
| <p><strong>Q: What is VR180 format?</strong><br> | |
| A: VR180 is a video format with two views side-by-side - one for each eye. It creates a 3D effect when viewed with VR headsets.</p> | |
| <p><strong>Q: Can I preview the result before downloading?</strong><br> | |
| A: Yes! After conversion, you can preview your VR180 video and download it when you're satisfied.</p> | |
| <p><strong>Q: What VR headsets are compatible?</strong><br> | |
| A: VR180 videos work with most VR headsets including Oculus, HTC Vive, and Google Cardboard.</p> | |
| </div> | |
| </div> | |
| """) | |
| # Tips for best results | |
| gr.HTML(""" | |
| <div class="step-container"> | |
| <h3>π‘ Tips for Best Results</h3> | |
| <div class="instruction-text"> | |
| <p>β’ <strong>Good Lighting:</strong> Well-lit videos convert better than dark or dimly lit ones</p> | |
| <p>β’ <strong>Clear Subjects:</strong> Videos with distinct objects and people work best</p> | |
| <p>β’ <strong>Stable Footage:</strong> Avoid shaky or blurry videos for better depth estimation</p> | |
| <p>β’ <strong>Reasonable Length:</strong> Shorter videos (under 5 minutes) process faster</p> | |
| <p>β’ <strong>High Quality:</strong> Higher resolution input generally produces better VR180 output</p> | |
| </div> | |
| </div> | |
| """) | |
| # Event handlers | |
| def process_video_with_ui(video_file, progress=gr.Progress()): | |
| """Process video and return UI updates""" | |
| if video_file is None: | |
| return (None, None, "Please upload a video file first.", | |
| gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)) | |
| # Process the video | |
| output_video, preview_video, status_msg, show_preview, show_preview_btn, show_download_btn, show_clear_btn = convert_video_to_vr180(video_file, progress) | |
| if output_video: | |
| return (output_video, preview_video, status_msg, | |
| gr.update(visible=True), show_preview_btn, show_download_btn, show_clear_btn) | |
| else: | |
| return (None, None, status_msg, | |
| gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)) | |
| def reset_all(): | |
| """Clear all inputs and outputs""" | |
| return (None, None, "Ready to convert videos! Upload a video file to get started.", | |
| gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)) | |
| convert_btn.click( | |
| fn=process_video_with_ui, | |
| inputs=[video_input], | |
| outputs=[video_output, video_output, status_text, preview_section, preview_btn, download_btn, clear_btn], | |
| show_progress=True | |
| ) | |
| reset_btn.click( | |
| fn=reset_all, | |
| inputs=[], | |
| outputs=[video_input, video_output, status_text, preview_section, preview_btn, download_btn, clear_btn] | |
| ) | |
| clear_btn.click( | |
| fn=reset_all, | |
| inputs=[], | |
| outputs=[video_input, video_output, status_text, preview_section, preview_btn, download_btn, clear_btn] | |
| ) | |
| # JavaScript will be handled through HTML onclick events | |
| # Preview button functionality | |
| preview_btn.click( | |
| fn=lambda: gr.update(visible=True), | |
| inputs=[], | |
| outputs=[preview_section] | |
| ) | |
| # Download button functionality | |
| download_btn.click( | |
| fn=lambda: gr.update(value="β Download started! Your VR180 video should begin downloading shortly."), | |
| inputs=[], | |
| outputs=[status_text] | |
| ) | |
| return demo | |
| def update_model_status(): | |
| """Update model status in the interface""" | |
| if model_manager.is_ready(): | |
| return "β Ready" | |
| elif model_manager.is_loading(): | |
| return "π Loading..." | |
| else: | |
| return "β οΈ Fallback Mode" | |
| if __name__ == "__main__": | |
| # Initialize models in background | |
| print("π Starting VR180 Converter...") | |
| print("π Preloading AI models...") | |
| # Create the interface | |
| demo = create_interface() | |
| # Launch the app | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| show_error=True, | |
| debug=False | |
| ) |