Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import zipfile | |
| import tempfile | |
| import shutil | |
| from pathlib import Path | |
| from PIL import Image, ImageOps | |
| import subprocess, json | |
| TARGET_WIDTH, TARGET_HEIGHT = 1080, 1440 | |
| try: | |
| from pillow_heif import register_heif_opener | |
| register_heif_opener() | |
| except ImportError: | |
| pass | |
| def process_files(files): | |
| if not files: | |
| return None, "No files uploaded." | |
| out_dir = Path(tempfile.mkdtemp()) | |
| stats = {"padded": 0, "copied": 0, "failed": 0} | |
| for f in files: | |
| p = Path(f.name) | |
| ext = p.suffix.lower() | |
| try: | |
| if ext in {'.jpg','.jpeg','.png','.heic','.webp'}: | |
| result = pad_image(p, out_dir) | |
| elif ext in {'.mp4','.mov','.avi','.mkv'}: | |
| result = pad_video(p, out_dir) | |
| else: | |
| continue | |
| stats[result] += 1 | |
| except Exception as e: | |
| stats["failed"] += 1 | |
| # Zip everything | |
| zip_path = out_dir / "instagram_ready.zip" | |
| with zipfile.ZipFile(zip_path, 'w') as zf: | |
| for f in out_dir.iterdir(): | |
| if f.name != "instagram_ready.zip": | |
| zf.write(f, f.name) | |
| summary = f"β Padded: {stats['padded']} | π Copied as-is: {stats['copied']} | β Failed: {stats['failed']}" | |
| return str(zip_path), summary | |
| def pad_image(input_path, out_dir): | |
| img = ImageOps.exif_transpose(Image.open(input_path)) | |
| w, h = img.size | |
| out_path = out_dir / f"padded_{input_path.stem}.jpg" | |
| if h >= w: | |
| shutil.copy2(input_path, out_dir / input_path.name) | |
| return "copied" | |
| scale = TARGET_WIDTH / w | |
| img = img.resize((TARGET_WIDTH, int(h * scale)), Image.LANCZOS) | |
| if img.mode != 'RGB': img = img.convert('RGB') | |
| canvas = Image.new('RGB', (TARGET_WIDTH, TARGET_HEIGHT), (0,0,0)) | |
| canvas.paste(img, (0, (TARGET_HEIGHT - img.height) // 2)) | |
| canvas.save(out_path, quality=95) | |
| return "padded" | |
| def pad_video(input_path, out_dir): | |
| r = subprocess.run(['ffprobe','-v','error','-select_streams','v:0', | |
| '-show_entries','stream=width,height','-of','json',str(input_path)], | |
| capture_output=True, text=True) | |
| d = json.loads(r.stdout)['streams'][0] | |
| out_path = out_dir / f"padded_{input_path.name}" | |
| if d['height'] >= d['width']: | |
| shutil.copy2(input_path, out_dir / input_path.name) | |
| return "copied" | |
| subprocess.run(['ffmpeg','-i',str(input_path), | |
| '-vf',f"scale={TARGET_WIDTH}:-2,pad={TARGET_WIDTH}:{TARGET_HEIGHT}:0:(oh-ih)/2:black", | |
| '-c:v','libx264','-preset','fast','-crf','23', | |
| '-c:a','copy','-map_metadata','0','-y',str(out_path)], | |
| capture_output=True) | |
| return "padded" | |
| with gr.Blocks(title="Instagram Padder πΈ", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# πΈ Instagram Portrait Padder\nUploads landscape photos & videos β pads to 1080Γ1440 (3:4) with black bars") | |
| files = gr.File(file_count="multiple", label="Drop photos & videos here") | |
| btn = gr.Button("Process & Download ZIP", variant="primary") | |
| out = gr.File(label="Download your ZIP") | |
| status = gr.Textbox(label="Summary", interactive=False) | |
| btn.click(process_files, inputs=files, outputs=[out, status]) | |
| demo.launch() |