natdon's picture
Upload 3 files
960b36a verified
Raw
History Blame Contribute Delete
3.21 kB
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()