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()