natdon commited on
Commit
960b36a
ยท
verified ยท
1 Parent(s): 0929c05

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +87 -0
  2. packages.txt +1 -0
  3. requirements.txt +3 -0
app.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import zipfile
3
+ import tempfile
4
+ import shutil
5
+ from pathlib import Path
6
+ from PIL import Image, ImageOps
7
+ import subprocess, json
8
+
9
+ TARGET_WIDTH, TARGET_HEIGHT = 1080, 1440
10
+
11
+ try:
12
+ from pillow_heif import register_heif_opener
13
+ register_heif_opener()
14
+ except ImportError:
15
+ pass
16
+
17
+ def process_files(files):
18
+ if not files:
19
+ return None, "No files uploaded."
20
+
21
+ out_dir = Path(tempfile.mkdtemp())
22
+ stats = {"padded": 0, "copied": 0, "failed": 0}
23
+
24
+ for f in files:
25
+ p = Path(f.name)
26
+ ext = p.suffix.lower()
27
+ try:
28
+ if ext in {'.jpg','.jpeg','.png','.heic','.webp'}:
29
+ result = pad_image(p, out_dir)
30
+ elif ext in {'.mp4','.mov','.avi','.mkv'}:
31
+ result = pad_video(p, out_dir)
32
+ else:
33
+ continue
34
+ stats[result] += 1
35
+ except Exception as e:
36
+ stats["failed"] += 1
37
+
38
+ # Zip everything
39
+ zip_path = out_dir / "instagram_ready.zip"
40
+ with zipfile.ZipFile(zip_path, 'w') as zf:
41
+ for f in out_dir.iterdir():
42
+ if f.name != "instagram_ready.zip":
43
+ zf.write(f, f.name)
44
+
45
+ summary = f"โœ… Padded: {stats['padded']} | ๐Ÿ“‹ Copied as-is: {stats['copied']} | โŒ Failed: {stats['failed']}"
46
+ return str(zip_path), summary
47
+
48
+ def pad_image(input_path, out_dir):
49
+ img = ImageOps.exif_transpose(Image.open(input_path))
50
+ w, h = img.size
51
+ out_path = out_dir / f"padded_{input_path.stem}.jpg"
52
+ if h >= w:
53
+ shutil.copy2(input_path, out_dir / input_path.name)
54
+ return "copied"
55
+ scale = TARGET_WIDTH / w
56
+ img = img.resize((TARGET_WIDTH, int(h * scale)), Image.LANCZOS)
57
+ if img.mode != 'RGB': img = img.convert('RGB')
58
+ canvas = Image.new('RGB', (TARGET_WIDTH, TARGET_HEIGHT), (0,0,0))
59
+ canvas.paste(img, (0, (TARGET_HEIGHT - img.height) // 2))
60
+ canvas.save(out_path, quality=95)
61
+ return "padded"
62
+
63
+ def pad_video(input_path, out_dir):
64
+ r = subprocess.run(['ffprobe','-v','error','-select_streams','v:0',
65
+ '-show_entries','stream=width,height','-of','json',str(input_path)],
66
+ capture_output=True, text=True)
67
+ d = json.loads(r.stdout)['streams'][0]
68
+ out_path = out_dir / f"padded_{input_path.name}"
69
+ if d['height'] >= d['width']:
70
+ shutil.copy2(input_path, out_dir / input_path.name)
71
+ return "copied"
72
+ subprocess.run(['ffmpeg','-i',str(input_path),
73
+ '-vf',f"scale={TARGET_WIDTH}:-2,pad={TARGET_WIDTH}:{TARGET_HEIGHT}:0:(oh-ih)/2:black",
74
+ '-c:v','libx264','-preset','fast','-crf','23',
75
+ '-c:a','copy','-map_metadata','0','-y',str(out_path)],
76
+ capture_output=True)
77
+ return "padded"
78
+
79
+ with gr.Blocks(title="Instagram Padder ๐Ÿ“ธ", theme=gr.themes.Soft()) as demo:
80
+ gr.Markdown("# ๐Ÿ“ธ Instagram Portrait Padder\nUploads landscape photos & videos โ†’ pads to 1080ร—1440 (3:4) with black bars")
81
+ files = gr.File(file_count="multiple", label="Drop photos & videos here")
82
+ btn = gr.Button("Process & Download ZIP", variant="primary")
83
+ out = gr.File(label="Download your ZIP")
84
+ status = gr.Textbox(label="Summary", interactive=False)
85
+ btn.click(process_files, inputs=files, outputs=[out, status])
86
+
87
+ demo.launch()
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ ffmpeg
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ Pillow
3
+ pillow-heif