Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import subprocess | |
| import os | |
| from pathlib import Path | |
| import tempfile | |
| import json | |
| # Comprehensive format support | |
| VIDEO_FORMATS = [ | |
| "mp4", "avi", "mkv", "mov", "wmv", "flv", "webm", "m4v", "mpg", "mpeg", | |
| "3gp", "ogv", "ts", "vob", "divx", "f4v", "asf", "rm", "rmvb" | |
| ] | |
| AUDIO_FORMATS = [ | |
| "mp3", "wav", "aac", "flac", "ogg", "m4a", "wma", "opus", "aiff", | |
| "alac", "ape", "ac3", "dts", "amr", "mka", "tta", "wv" | |
| ] | |
| IMAGE_FORMATS = [ | |
| "jpg", "jpeg", "png", "gif", "bmp", "tiff", "tif", "webp", "svg", | |
| "ico", "heic", "heif", "raw", "cr2", "nef", "psd", "eps" | |
| ] | |
| DOCUMENT_FORMATS = [ | |
| "pdf", "txt", "doc", "docx", "rtf", "odt", "html", "md" | |
| ] | |
| ALL_FORMATS = sorted(set(VIDEO_FORMATS + AUDIO_FORMATS + IMAGE_FORMATS + DOCUMENT_FORMATS)) | |
| def get_file_info(file_path): | |
| """Get detailed information about the uploaded file.""" | |
| if not file_path: | |
| return "π *Upload a file to see details*" | |
| try: | |
| file_size = os.path.getsize(file_path) | |
| file_ext = Path(file_path).suffix.lower().replace(".", "") | |
| size_mb = file_size / (1024 * 1024) | |
| info = f""" | |
| π **File Information** | |
| - **Name:** {Path(file_path).name} | |
| - **Format:** {file_ext.upper()} | |
| - **Size:** {size_mb:.2f} MB | |
| """ | |
| # Try to get media info using ffprobe | |
| try: | |
| result = subprocess.run( | |
| ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", file_path], | |
| capture_output=True, | |
| text=True, | |
| timeout=10 | |
| ) | |
| if result.returncode == 0: | |
| data = json.loads(result.stdout) | |
| if "format" in data: | |
| duration = data["format"].get("duration", "N/A") | |
| if duration != "N/A": | |
| info += f"\n- **Duration:** {float(duration):.2f} seconds" | |
| if "streams" in data: | |
| for stream in data["streams"]: | |
| if stream["codec_type"] == "video": | |
| info += f"\n- **Video Codec:** {stream.get('codec_name', 'N/A')}" | |
| info += f"\n- **Resolution:** {stream.get('width', 'N/A')}x{stream.get('height', 'N/A')}" | |
| break | |
| for stream in data["streams"]: | |
| if stream["codec_type"] == "audio": | |
| info += f"\n- **Audio Codec:** {stream.get('codec_name', 'N/A')}" | |
| info += f"\n- **Sample Rate:** {stream.get('sample_rate', 'N/A')} Hz" | |
| break | |
| except Exception as e: | |
| info += f"\n\n*Could not retrieve detailed media info*" | |
| return info | |
| except Exception as e: | |
| return f"β Error reading file: {str(e)}" | |
| def convert_media(input_file, output_format, quality="high", resolution=None): | |
| """ | |
| Convert media file to specified format. | |
| Args: | |
| input_file: Path to input file | |
| output_format: Desired output format | |
| quality: Quality setting (high, medium, low) | |
| resolution: Optional resolution for video (e.g., "1920x1080") | |
| """ | |
| if not input_file: | |
| raise gr.Error("β Please upload a file first!") | |
| if not output_format: | |
| raise gr.Error("β Please select an output format!") | |
| # Create temporary output file | |
| output_file = tempfile.NamedTemporaryFile( | |
| delete=False, | |
| suffix=f".{output_format}" | |
| ).name | |
| try: | |
| # Build ffmpeg command based on format and quality | |
| cmd = ["ffmpeg", "-i", input_file, "-y"] | |
| # Quality settings | |
| if quality == "high": | |
| if output_format in VIDEO_FORMATS: | |
| cmd.extend(["-crf", "18", "-preset", "slow"]) | |
| elif output_format in AUDIO_FORMATS: | |
| cmd.extend(["-b:a", "320k"]) | |
| elif quality == "medium": | |
| if output_format in VIDEO_FORMATS: | |
| cmd.extend(["-crf", "23", "-preset", "medium"]) | |
| elif output_format in AUDIO_FORMATS: | |
| cmd.extend(["-b:a", "192k"]) | |
| else: # low | |
| if output_format in VIDEO_FORMATS: | |
| cmd.extend(["-crf", "28", "-preset", "fast"]) | |
| elif output_format in AUDIO_FORMATS: | |
| cmd.extend(["-b:a", "128k"]) | |
| # Resolution settings for video | |
| if resolution and output_format in VIDEO_FORMATS: | |
| cmd.extend(["-s", resolution]) | |
| # Format-specific settings | |
| if output_format == "mp3": | |
| cmd.extend(["-codec:a", "libmp3lame"]) | |
| elif output_format == "aac": | |
| cmd.extend(["-codec:a", "aac"]) | |
| elif output_format == "flac": | |
| cmd.extend(["-codec:a", "flac"]) | |
| elif output_format == "opus": | |
| cmd.extend(["-codec:a", "libopus"]) | |
| elif output_format == "webm": | |
| cmd.extend(["-codec:v", "libvpx-vp9", "-codec:a", "libopus"]) | |
| elif output_format == "mp4": | |
| cmd.extend(["-codec:v", "libx264", "-codec:a", "aac"]) | |
| elif output_format == "wav": | |
| cmd.extend(["-codec:a", "pcm_s16le"]) | |
| elif output_format in IMAGE_FORMATS: | |
| # For image conversion, just specify format | |
| pass | |
| cmd.append(output_file) | |
| # Execute conversion | |
| result = subprocess.run( | |
| cmd, | |
| capture_output=True, | |
| text=True, | |
| timeout=300 # 5 minute timeout | |
| ) | |
| if result.returncode != 0: | |
| raise Exception(f"Conversion failed: {result.stderr}") | |
| # Verify output file exists and has content | |
| if not os.path.exists(output_file) or os.path.getsize(output_file) == 0: | |
| raise Exception("Conversion produced empty file") | |
| output_size = os.path.getsize(output_file) / (1024 * 1024) | |
| return output_file, f"β **Conversion Successful!**\n\nConverted to **{output_format.upper()}** format with **{quality}** quality.\n\nπ¦ Output size: **{output_size:.2f} MB**" | |
| except subprocess.TimeoutExpired: | |
| if os.path.exists(output_file): | |
| os.unlink(output_file) | |
| raise gr.Error("β±οΈ Conversion timed out. File may be too large or complex.") | |
| except Exception as e: | |
| if os.path.exists(output_file): | |
| os.unlink(output_file) | |
| raise gr.Error(f"β Conversion failed: {str(e)}") | |
| def update_format_suggestions(input_file): | |
| """Update suggested formats based on input file type.""" | |
| if not input_file: | |
| return gr.Dropdown(choices=ALL_FORMATS, value="mp4") | |
| file_ext = Path(input_file).suffix.lower().replace(".", "") | |
| # Suggest related formats | |
| if file_ext in VIDEO_FORMATS: | |
| suggested = ["mp4", "webm", "avi", "mkv", "mov", "gif"] | |
| elif file_ext in AUDIO_FORMATS: | |
| suggested = ["mp3", "wav", "aac", "flac", "ogg", "m4a"] | |
| elif file_ext in IMAGE_FORMATS: | |
| suggested = ["jpg", "png", "webp", "gif", "bmp", "tiff"] | |
| else: | |
| suggested = ["mp4", "mp3", "jpg", "png"] | |
| # Add all formats but prioritize suggested ones | |
| all_choices = suggested + [f for f in ALL_FORMATS if f not in suggested] | |
| return gr.Dropdown(choices=all_choices, value=suggested[0]) | |
| # Custom CSS for better UI/UX | |
| custom_css = """ | |
| .main-container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| } | |
| .header-section { | |
| text-align: center; | |
| padding: 2rem 1rem; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| border-radius: 12px; | |
| margin-bottom: 2rem; | |
| color: white; | |
| } | |
| .header-section h1 { | |
| font-size: 2.5rem; | |
| margin-bottom: 0.5rem; | |
| font-weight: 700; | |
| } | |
| .header-section p { | |
| font-size: 1.1rem; | |
| opacity: 0.95; | |
| } | |
| .conversion-panel { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 12px; | |
| padding: 1.5rem; | |
| margin-bottom: 1rem; | |
| } | |
| .info-box { | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| border-radius: 8px; | |
| padding: 1rem; | |
| margin: 1rem 0; | |
| } | |
| .status-success { | |
| color: #10b981; | |
| font-weight: 600; | |
| } | |
| .status-error { | |
| color: #ef4444; | |
| font-weight: 600; | |
| } | |
| footer { | |
| text-align: center; | |
| padding: 2rem; | |
| margin-top: 2rem; | |
| opacity: 0.8; | |
| } | |
| .format-badge { | |
| display: inline-block; | |
| padding: 0.25rem 0.75rem; | |
| background: #667eea; | |
| color: white; | |
| border-radius: 20px; | |
| font-size: 0.875rem; | |
| margin: 0.25rem; | |
| } | |
| .feature-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 1rem; | |
| margin: 1rem 0; | |
| } | |
| .feature-item { | |
| background: rgba(102, 126, 234, 0.1); | |
| padding: 1rem; | |
| border-radius: 8px; | |
| text-align: center; | |
| } | |
| """ | |
| # Build the Gradio interface | |
| with gr.Blocks(fill_height=True) as demo: | |
| gr.HTML(""" | |
| <div class="header-section"> | |
| <h1>π¬ Universal Media Converter</h1> | |
| <p>Convert any media file to any format β’ Support for 60+ formats β’ Professional quality</p> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color: white; text-decoration: none; opacity: 0.9; font-size: 0.9rem;"> | |
| β¨ Built with anycoder | |
| </a> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### π€ Input Settings") | |
| input_file = gr.File( | |
| label="Upload Media File", | |
| file_types=["video", "audio", "image"], | |
| type="filepath" | |
| ) | |
| file_info = gr.Markdown( | |
| value="π *Upload a file to see details*", | |
| elem_classes="info-box" | |
| ) | |
| gr.Markdown("### βοΈ Conversion Settings") | |
| output_format = gr.Dropdown( | |
| choices=ALL_FORMATS, | |
| value="mp4", | |
| label="Output Format", | |
| info="Select the desired output format" | |
| ) | |
| quality = gr.Radio( | |
| choices=["high", "medium", "low"], | |
| value="high", | |
| label="Quality", | |
| info="Higher quality = larger file size" | |
| ) | |
| with gr.Accordion("π¨ Advanced Options", open=False): | |
| resolution = gr.Dropdown( | |
| choices=[ | |
| "Original", | |
| "3840x2160 (4K)", | |
| "1920x1080 (Full HD)", | |
| "1280x720 (HD)", | |
| "854x480 (SD)", | |
| "640x360" | |
| ], | |
| value="Original", | |
| label="Video Resolution (for video files)", | |
| info="Resize video to specific resolution" | |
| ) | |
| convert_btn = gr.Button( | |
| "π Convert Media", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| clear_btn = gr.ClearButton( | |
| components=[input_file, output_format, quality, file_info], | |
| value="ποΈ Clear All" | |
| ) | |
| with gr.Column(scale=1): | |
| gr.Markdown("### π₯ Output") | |
| status_message = gr.Markdown( | |
| value="*Ready to convert your media*", | |
| elem_classes="info-box" | |
| ) | |
| output_file = gr.File( | |
| label="Converted File", | |
| type="filepath" | |
| ) | |
| gr.Markdown(""" | |
| ### π Supported Formats | |
| **Video:** MP4, AVI, MKV, MOV, WebM, FLV, WMV, and more | |
| **Audio:** MP3, WAV, AAC, FLAC, OGG, M4A, and more | |
| **Image:** JPG, PNG, GIF, WebP, BMP, TIFF, and more | |
| ### β¨ Features | |
| - π― 60+ format support | |
| - π¨ Quality control (high/medium/low) | |
| - π Resolution options for video | |
| - β‘ Fast conversion with FFmpeg | |
| - πΎ Detailed file info analysis | |
| - π Smart format suggestions | |
| - π Output size reporting | |
| ### π How to Use | |
| 1. **Upload** your media file | |
| 2. **Select** output format (auto-suggested) | |
| 3. **Choose** quality level | |
| 4. **Click** Convert Media button | |
| 5. **Download** your converted file | |
| """) | |
| # Event handlers | |
| input_file.change( | |
| fn=get_file_info, | |
| inputs=[input_file], | |
| outputs=[file_info] | |
| ) | |
| input_file.change( | |
| fn=update_format_suggestions, | |
| inputs=[input_file], | |
| outputs=[output_format] | |
| ) | |
| def process_conversion(input_file, output_format, quality, resolution): | |
| res = None if resolution == "Original" else resolution.split(" ")[0] | |
| return convert_media(input_file, output_format, quality, res) | |
| convert_btn.click( | |
| fn=process_conversion, | |
| inputs=[input_file, output_format, quality, resolution], | |
| outputs=[output_file, status_message] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch( | |
| theme=gr.themes.Soft( | |
| primary_hue="indigo", | |
| secondary_hue="purple", | |
| neutral_hue="slate", | |
| font=gr.themes.GoogleFont("Inter"), | |
| text_size="lg", | |
| spacing_size="lg", | |
| radius_size="lg" | |
| ).set( | |
| button_primary_background_fill="*primary_600", | |
| button_primary_background_fill_hover="*primary_700", | |
| block_title_text_weight="600", | |
| block_label_text_weight="600", | |
| ), | |
| css=custom_css, | |
| footer_links=[ | |
| {"label": "Built with anycoder", "url": "https://huggingface.co/spaces/akhaliq/anycoder"} | |
| ] | |
| ) |