Normalize video for social media

View Markdown

Users upload one video. Your app needs three: 9:16 for stories and reels, 1:1 for feed posts, 16:9 for YouTube. One input, three tasks.

โ€œPostmarkโ€ (a social media scheduler like Later) normalizes every upload into all three formats automatically.

Target sizes

PlatformAspect ratioDimensionsUse case
TikTok / Reels / Stories9:161080x1920Vertical short-form
Instagram / Facebook Feed1:11080x1080Square feed posts
YouTube / Web16:91920x1080Landscape long-form

API

Vertical for TikTok/Reels (9:16):

ittybit video \
  -i https://postmark-app.com/uploads/campaign-video.mov \
  --width 1080 \
  --height 1920 \
  --fit cover \
  --format mp4
const task = {
  input: 'https://postmark-app.com/uploads/campaign-video.mov',
  kind: 'video',
  options: {
    width: 1080,
    height: 1920,
    fit: 'cover',
    format: 'mp4',
  },
};

const res = await fetch('https://api.ittybit.com/jobs', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(task),
});
const data = await res.json();
import requests

task = {
    "input": "https://postmark-app.com/uploads/campaign-video.mov",
    "kind": "video",
    "options": {
        "width": 1080,
        "height": 1920,
        "fit": "cover",
        "format": "mp4",
    },
}

res = requests.post(
    "https://api.ittybit.com/jobs",
    headers={"Authorization": f"Bearer {api_key}"},
    json=task,
)
data = res.json()
TASK='{
  "input": "https://postmark-app.com/uploads/campaign-video.mov",
  "kind": "video",
  "options": {
    "width": 1080,
    "height": 1920,
    "fit": "cover",
    "format": "mp4"
  }
}'

curl -X POST https://api.ittybit.com/jobs \
  -H "Authorization: Bearer $ITTYBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$TASK"

Square for feed (1:1):

{
  "input": "https://postmark-app.com/uploads/campaign-video.mov",
  "kind": "video",
  "options": { "width": 1080, "height": 1080, "fit": "cover", "format": "mp4" }
}

Landscape for YouTube (16:9):

{
  "input": "https://postmark-app.com/uploads/campaign-video.mov",
  "kind": "video",
  "options": { "width": 1920, "height": 1080, "fit": "cover", "format": "mp4" }
}

CLI

ittybit video \
  -i campaign-video.mov \
  -o reels.mp4 \
  --width 1080 \
  --height 1920 \
  --fit cover

ittybit video \
  -i campaign-video.mov \
  -o square.mp4 \
  --width 1080 \
  --height 1080 \
  --fit cover

ittybit video \
  -i campaign-video.mov \
  -o youtube.mp4 \
  --width 1920 \
  --height 1080 \
  --fit cover

Fit modes

FitBehaviorBest for
coverFills frame, crops edgesSocial media โ€” no letterboxing
containFits inside frame, may letterboxPreserving full frame
fillStretches to exact dimensionsRarely useful

Use cover for social. It fills the target frame and crops the overflow โ€” no black bars.

All three in one script

Fire all three tasks in parallel. Each runs independently.

const formats = [
  { name: 'reels', width: 1080, height: 1920 },
  { name: 'square', width: 1080, height: 1080 },
  { name: 'youtube', width: 1920, height: 1080 },
];

const tasks = await Promise.all(
  formats.map(async ({ width, height }) => {
    const res = await fetch('https://api.ittybit.com/jobs', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        input: 'https://postmark-app.com/uploads/campaign-video.mov',
        kind: 'video',
        options: { width, height, fit: 'cover', format: 'mp4' },
      }),
    });
    return res.json();
  }),
);

Three tasks, three outputs, one upload. Poll each task ID or use webhooks to get notified when they finish.