agentleFS
mertbuildsmertbuilds/skills/feed-safe-images/SKILL.md

feed-safe-images

Skill2 starsChanged 7 months agoMITView on GitHub

Pad photos onto a white canvas at the exact ratio social-media feeds use, so the whole photo is visible on X, Instagram, Threads, or LinkedIn before anyone taps. Use when the user wants to post photos to social media, asks why their image got cropped in the timeline, says 'make this feed safe', 'add white borders for X', 'prep these for instagram', or attaches 2-4 photos for one post, or wants a film-scan style border on an instagram photo.

  • Reads credentials

The file

We split this skill into the rules it gives an agent

1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Run pad.sh on the originals, never on already-resized exports.

    line 69

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Feed-safe images

Social feeds crop photos to a fixed ratio in the timeline. Only the full-screen view shows the whole frame, and most people never tap. The fix is to put the photo on a solid canvas at the ratio the feed will show, so the crop hits the padding instead of the photo.

This skill ships scripts/pad.sh, an ffmpeg one-liner wrapped with the right canvas per platform layout.

Ratios

LayoutPlatform and caseCanvas per imageRatio
x1X, single image, photo at 95% width2400 x 19205:4
xcX, every slide of a 2-4 image carousel, photo at 95% width2400 x 19205:4
x2 x3big x3small x4X, legacy grid layouts. Obsolete, see note below
igInstagram feed post or carousel slide, photo at 95% width2160 x 28803:4
igblackInstagram, same canvas as ig but black, photo corners rounded (91 px). The uncropped companion to film for horizontal photos2160 x 28803:4
filmInstagram, single image or carousel slide, film-scan look: photo full-bleed (cover-cropped to 3:4) with a thin black border and rounded inner corners. Instagram only, X crops it (5:4 slot)1512 x 20163:4
threadsThreads post or carousel slide2160 x 27004:5
liLinkedIn, portrait (most feed space)2160 x 27004:5
li-wideLinkedIn, landscape2400 x 12561.91:1
squareany platform, 1:12160 x 21601:1

Canvases are 2x to 3x the platform's display size so they survive recompression. The photo is fit to 95.3% of the canvas on its constraining axis and centered (95% on x1, xc and ig, which leaves 2.5% side margins: in the X and Instagram feeds 95.3% read as zero margin, and 90% or 92% read as too-thick margins, both live-tested). The reference for the thinner look was a photographer's Instagram post at 1512 x 2016 with about 2.4% side margins; override with --fit.

X multi-image posts are a carousel now, not a grid. Since 2026 (X Lite Android April, wider rollout by June, x.com web by late August) a post with 2-4 images shows as a horizontal swipe carousel. Each slide renders at its own full aspect ratio with no crop, so the old grid pads (7:8, 4:7, 2:1) are wrong: they show as squat white-barred slides. The carousel slot itself is 5:4 with object-fit: cover (measured on x.com 2026-08-29: 479 x 383 at every viewport width), so anything wider than 5:4 is cropped (16:9 loses about 30% of its width) while portrait down to 7:8 passes through. Pad every slide with xc (5:4). x1 is 5:4 too now (it was 16:9): a 3:2 or 4:3 photo on a 16:9 canvas came out height-limited at about 76% width with fat side margins, and 5:4 makes it width-limited with bands top and bottom, the same look as the carousel. The x2 / x3big / x3small / x4 layouts stay in the script for anyone still seeing the grid, but do not reach for them by default.

Live-tested: x1 (single), xc (3-slide carousel, every photo whole), ig (2-slide carousel; the earlier 4:5 canvas was cropped about 6% per side by Instagram's 3:4 feed frame and lost its margins, hence 3:4 at 95%, 92% until 2026-09-07), film (single image and 2-slide carousel, 2026-09-07 and 2026-09-08), igblack (single image, 2026-09-08). The rest come from platform docs and third-party size guides current as of 2026 and can drift; third-party size guides still described the X grid months after it was gone, so trust a live post over a guide. If a preview still crops, check the platform's current layout and adjust the table.

Usage

scripts/pad.sh <layout> [-o DIR] [--fit 0.953] [--bg white] [--radius 0] [-q 2] <photo>...

Examples:

# two- to four-photo X post: same layout for every slide
scripts/pad.sh xc IMG_001.jpg IMG_002.jpg IMG_003.jpg

# instagram carousel, custom output folder, off-white canvas
scripts/pad.sh ig -o ./ig-out --bg '#fafafa' *.jpg

# instagram single image with the film-scan frame
scripts/pad.sh film IMG_004.jpg

# horizontal photo that would lose its edges to the film crop: black canvas, rounded corners
scripts/pad.sh igblack IMG_005.jpg

Output is <out>/<name>-<layout>.jpg, JPEG quality 2 (near lossless). Requires ffmpeg.

Film frame

film is the opposite of padding: the photo is cover-cropped to 3:4 (full bleed, no canvas showing), then scripts/film-frame-1512x2016.png is overlaid on top. The mask is a 34 px black border with inner corners at radius 64 and a 2.5 px feathered edge, so the photo looks like a scan sitting inside a frame. --fit and --bg are accepted but ignored for this layout. Instagram only: X's 5:4 slot would crop the top and bottom border, so use x1 or xc there.

film cover-crops, so a 16:9 photo loses about 40% of its width and anything near the left or right edge is gone. When that matters, use igblack instead: the ig canvas in black with the photo corners rounded (--radius 91, the frame's corner radius scaled to the canvas), whole photo visible, same dark look in the feed. --radius works on any padded layout, and --bg still overrides the canvas color.

To regenerate or restyle the mask, run uv run --with pillow python scripts/make-film-frame.py (or plain python with Pillow installed) and edit border, radius and feather in the script. The output file name encodes the canvas size, and pad.sh looks the mask up by that name.

How to apply it

  1. Ask how many photos go in the post and which platform. Pick the layout from the table. One layout covers single posts and multi-image posts on every platform; for X that is x1 for a single image and xc for every slide of a 2-4 image post. For Instagram, film is the alternative look when the user asks for a film, vintage, or bordered frame; if the photo is horizontal and the edges matter, igblack keeps the whole frame.
  2. Run pad.sh on the originals, never on already-resized exports.
  3. Show the user one padded result before uploading. Read the file back and check: photo centered, canvas color solid, no crop.
  4. Upload the padded files in post order. Keep the originals; the padded versions are only for the feed.

Notes

  • Mixed ratios in one multi-image post get cropped unpredictably. Pad every image to the same layout.
  • Portrait sources on a landscape canvas (or the reverse) are fit by height instead of width; the script handles both.
  • Dark mode: a white canvas shows as a white card on dark timelines. That is the intended look. Use --bg black if the account's feed is dark.
  • Screenshots and graphics with text are fine too, but consider --fit 0.9 so text does not sit against the canvas edge.

Hosting for Instagram's API

Instagram's Graph API has no upload endpoint: create_media_container takes an image_url, and Meta fetches it once. scripts/host.sh <file>... uploads each file to a Cloudflare R2 bucket and prints one public URL per file, in order. Setup once: wrangler login, create a bucket with a public dev URL and a 1-day expiry lifecycle rule (wrangler r2 bucket create NAME, wrangler r2 bucket lifecycle add NAME --name expire-1d --expire-days 1, wrangler r2 bucket dev-url enable NAME), then write ~/.config/cloudflare/r2-staging.env with R2_STAGING_BUCKET and R2_STAGING_PUBLIC_URL. Keep that file out of any repo. If *.r2.dev is blocked on your network, verify by creating the Instagram container and polling its status to FINISHED, not with curl.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.