Do this first

Setting up, about five minutes

Facebook Video does not do the encoding itself. It hands the work to ffmpeg, the free open source tool that sits underneath most video software on earth. ffmpeg is not included in the download, because its licence does not allow it to be bundled inside an app like this one, and it is almost certainly not on your Mac already.

You will need Terminal. It is in your Applications folder, inside a folder called Utilities. Open it and leave it open.

1
Install Homebrew

Go to brew.sh in your browser. There is a single install command on that page. Copy it, paste it into Terminal and press Return.

It will ask for your Mac password. As you type, nothing appears on screen. That is normal. Keep typing and press Return.

When it finishes it may print two more commands and ask you to run them. If it does, run them. If it does not, carry on.

2
Install ffmpeg

Paste this into Terminal and press Return.

brew install ffmpeg

This is the slow one. It can take several minutes and it prints a lot of text. Let it finish.

3
Tell Facebook Video where ffmpeg is

Paste this into Terminal and press Return.

mkdir -p ~/bin && ln -s "$(which ffmpeg)" ~/bin/ffmpeg

Nothing will appear. Silence means it worked.

That is the setup finished. You can close Terminal and never open it again. Drag a video onto Facebook Video and it will work.

Day to day

Using it

Drag a video onto the Facebook Video icon. A new file appears beside your original, named the same but ending _facebook.mp4. Your original is untouched. When it is done you get a notification and the folder opens.

You can drop several clips at once, or a whole folder, and it will work through them one after another.

Under the bonnet

What it is actually doing

  • It measures how long your clip is, then works out a video bitrate that will land the finished file near 90 MB. That leaves a margin under Facebook's 100 MB limit without wasting quality by going far below it
  • It encodes in two passes. One pass has to guess where in the clip to spend the bitrate. The second pass already knows, so the same file size looks better
  • The long edge goes to 1920 pixels, or 1280 on longer clips where the bitrate works out low, because a clean 720p beats a starved 1080p. It never scales a video up
  • If your footage is HDR, which most recent iPhone video is, it converts it to normal Rec.709 colour. This is the step that stops video arriving on Facebook looking grey and washed out
  • Audio is 128 kbps stereo AAC, and the file is written so it starts playing before it has finished downloading
What it does not do
  • It does not trim, crop or reframe. What you drop on it comes back the same shape, only smaller
  • It does not touch your original file
  • It does not upload anything. You still post the video yourself
If something goes wrong

The five things that happen

A box says ffmpeg was not found
The setup above has not been done, or it did not finish. Open Terminal and paste this.
which ffmpeg
If it prints a path, Step 3 is the one that did not take. Run Step 3 again. If it prints nothing at all, Step 2 did not take. Run Step 2 again.
A box says the video is too long to fit at a watchable quality
Your clip is long enough that squeezing it under 100 MB would make it look bad, so it stopped rather than hand you something unusable. Trim the clip shorter and try again. Facebook is not the place for a twenty minute video anyway.
A box says it could not read the length of the video
The file is probably not a video, or it is damaged. Try playing it in QuickTime first.
It finished, but the file came out over 100 MB
Rare, and it tells you when it happens. Very short clips with a lot of movement can overshoot. Trim a few seconds off and run it again.
Nothing happens at all when I drop a file on it
Check you dropped it onto the app icon itself rather than into the window behind it. If the app lives in your Applications folder, drag it to your Dock and drop videos onto it there instead.
For the curious

Changing the target size

If you want a different size, you can change it yourself. Right click the app, choose Open With, then Automator. You will see the script. Near the top there is a line that reads:

TARGET_MB=90

Change the number, save, and close Automator. Everything else carries on as before.

Editing the app this way breaks Apple's signature on it. That is harmless on your own Mac, but it does mean you should not pass the edited copy on to anyone else.

Full transparency

The script, in full

This is everything Facebook Video does, all 112 lines of it. You should never run something on your own machine without being able to see what it is. Read it, or have someone read it for you, before you do.

#!/bin/zsh
# Facebook Video droplet - Automator "Run Shell Script" body.
# Shell: /bin/zsh   Pass input: as arguments
#
# Needs ffmpeg at ~/bin/ffmpeg (one-time setup).
# Makes an H.264 MP4 aimed at about 90 MB, so it stays under
# Facebook's 100 MB limit. Long edge 1920 px (1280 px for long clips),
# never scaled up. iPhone HDR footage is converted to normal colour.
# Writes "<name>_facebook.mp4" beside the original, never overwrites.

FFMPEG="${FFMPEG_PATH:-$HOME/bin/ffmpeg}"
TARGET_MB=90        # aim for this size
LIMIT_MB=100        # warn if a result is bigger than this
MAX_KBPS=6000       # no need to go higher than this for Facebook
AUDIO_KBPS=128

alert() {
  osascript - "$1" <<'EOF' >/dev/null 2>&1
on run argv
  display dialog (item 1 of argv) buttons {"OK"} default button 1 with title "Facebook Video"
end run
EOF
}

if [ ! -x "$FFMPEG" ]; then
  alert "ffmpeg was not found at:

$FFMPEG

Do the one time ffmpeg setup first."
  exit 1
fi

lastdir=""
count=0

for f in "$@"; do
  dir=$(dirname "$f")
  base=$(basename "$f")
  name="${base%.*}"
  out="$dir/${name}_facebook.mp4"
  n=1
  while [ -e "$out" ]; do
    out="$dir/${name}_facebook_$n.mp4"
    n=$((n+1))
  done

  info=$("$FFMPEG" -hide_banner -i "$f" 2>&1)
  secs=$(printf '%s\n' "$info" | awk '/Duration:/ { split($2, t, /[:,]/); print t[1]*3600 + t[2]*60 + t[3]; exit }')
  if [ -z "$secs" ] || ! awk -v s="$secs" 'BEGIN { exit !(s > 0) }'; then
    alert "Could not read the length of this video:

$f"
    continue
  fi

  # Video bitrate that lands the whole file near TARGET_MB.
  vkbps=$(awk -v s="$secs" -v t="$TARGET_MB" -v a="$AUDIO_KBPS" -v m="$MAX_KBPS" \
    'BEGIN { v = int(t * 8000 / s - a); if (v > m) v = m; print v }')
  if [ "$vkbps" -lt 300 ]; then
    alert "This video is too long to fit under ${LIMIT_MB} MB at a watchable quality:

$f"
    continue
  fi

  # Long edge 1920, or 1280 when the bitrate is low (looks better than starved 1080p).
  if [ "$vkbps" -lt 2500 ]; then edge=1280; else edge=1920; fi
  scale="scale='if(gte(iw,ih),min($edge,iw),-2)':'if(gte(iw,ih),-2,min($edge,ih))'"

  # iPhone HDR (HLG / Dolby Vision / PQ) -> normal colour, so it does not look washed out.
  if printf '%s\n' "$info" | grep -Eq 'arib-std-b67|smpte2084'; then
    vf="zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,$scale,format=yuv420p"
  else
    vf="$scale,format=yuv420p"
  fi

  passlog=$(mktemp /tmp/fbvideo_pass.XXXXXX)
  errfile=$(mktemp /tmp/fbvideo_err.XXXXXX)

  if "$FFMPEG" -hide_banner -nostdin -y -i "$f" -map 0:v:0 -vf "$vf" \
       -c:v libx264 -preset medium -b:v "${vkbps}k" -pass 1 -passlogfile "$passlog" \
       -an -f mp4 /dev/null > "$errfile" 2>&1 \
     && "$FFMPEG" -hide_banner -nostdin -y -i "$f" -map 0:v:0 -map '0:a:0?' -vf "$vf" \
       -c:v libx264 -preset medium -b:v "${vkbps}k" -pass 2 -passlogfile "$passlog" \
       -profile:v high -colorspace bt709 -color_primaries bt709 -color_trc bt709 \
       -c:a aac -b:a "${AUDIO_KBPS}k" -ac 2 -movflags +faststart "$out" >> "$errfile" 2>&1
  then
    lastdir="$dir"
    count=$((count+1))
    size_mb=$(( $(wc -c < "$out" | tr -d ' ') / 1000000 ))
    if [ "$size_mb" -ge "$LIMIT_MB" ]; then
      alert "Finished, but it came out at ${size_mb} MB, over the ${LIMIT_MB} MB limit:

$out"
    fi
  else
    msg=$(tail -n 5 "$errfile")
    rm -f "$out"
    alert "Could not convert:

$f

$msg"
  fi
  rm -f "$errfile" "$passlog"*
done

if [ -n "$lastdir" ]; then
  osascript -e "display notification \"$count video(s) ready for Facebook\" with title \"Facebook Video\"" >/dev/null 2>&1
  open "$lastdir" 2>/dev/null
fi
Privacy

Nothing leaves your Mac

No account, no sign in, no telemetry, no licence check. Facebook Video makes no network connections of any kind. It reads the file you drop on it and writes a new one beside it, and that is the whole of it.

The entire thing is a short script, and you are welcome to read it before you run anything.

Still stuck?

Tell me what the box on screen said, word for word. That one sentence is usually enough to work out what happened.