Spaces:
Sleeping
Sleeping
File size: 3,213 Bytes
960b36a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | 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() |