Final Cut Pro - how to create proxy media in FFmpeg

Filming footage in 4k resolution isn’t difficult - the GoPro has been able to film 4k30 since the GoPro 3, and 4k60 since the GoPro 6. Most recent mobile phones can film in 4k as well.

But editing this footage is a different matter. The H.264 / H.265 codecs that are used for such footage gain their efficiency by making heavy use of references to previous (and sometimes even future) frames in their encoding. This is fine for playback, providing you have a powerful enough machine, but if you want to edit said footage, you’re going to want to be able to freely step back and forward through the frames.

The solution is to make use of proxy media. This refers to generating a lower resolution copy of every clip, and using it instead of the original clip during the edit process. But for the full quality final export, the video is generated using the original clips.

When you import footage into Final Cut Pro, there’s a checkbox here to generate proxy media. By default, proxy media is generated using the Apple ProRes 422 LT codec. This DCT-based codec uses only intra-frame compression to allow immediate seeking to any frame, and is very light on CPU load for playback.

But suppose that, say, your main editing machine - a quad core i7 MacBook Pro with 16GB of RAM discrete graphics - was dead, and you were using the “backup” machine - a much slower dual core i5 with 8GB of RAM and no GPU. The proxy media sure is going to take a while longer if there’s 200GB of footage to sort through.

On the other hand, let’s say that you have a much more powerful Linux machine that could be doing all the hard work instead. FFmpeg has an encoder for ProRes. How do we generate the proxy media on the server, then have Final Cut Pro recognise and use it?

It’s a fairly simple process, but it’s not entirely straight forward, and I haven’t seen anyone else describe how to do it - hence I wanted to document it here for the benefit of others. Read on to find out how.

The Encoding:

Here is make_proxies.sh:

#!/bin/bash
set -euo pipefail

INPUT_DIR=$1
MAX_PROCS=2

find ${INPUT_DIR} -iname *.mp4 -print0 | xargs -0 -P ${MAX_PROCS} -n 1 $(dirname $0)/prores_encode.sh ${INPUT_DIR}

So here we run find to find all the files with a name of .MP4 (case insensitive, because it seems both the GoPro and DJI drones both use .MP4). We set xargs to use 2 processes - ffmpeg is multi-threaded but didn’t seem to be using all of the CPU with only one process running.

Then here is prores_encode.sh:

#!/bin/bash
set -euo pipefail

RESOLUTION=1280x720

OUTPUT_PATH=$1
ENCODE_FILE=$2

SUB_PATH=${ENCODE_FILE#"$OUTPUT_PATH"}
PROXY_OUTPUT_PATH=${OUTPUT_PATH}/proxy/$SUB_PATH
PROXY_OUTPUT_PATH_MOV=${PROXY_OUTPUT_PATH%.[mM][pP]4}.mov

mkdir -p "$(dirname -- "$PROXY_OUTPUT_PATH")"

ffmpeg -i "${ENCODE_FILE}" -vcodec prores -profile:v 0 -acodec pcm_s16le -s $RESOLUTION "$PROXY_OUTPUT_PATH_MOV"

You can adjust resolution as desired. I find 720p is a good compromise - the resultant files will be about as large as the original 4k source media - the compression is a lot less efficient however they will be playable with much much less CPU power. Also this has a small advantage over doing it in Final Cut Pro - you can render all your proxy clips at a fixed resolution, rather than only being able to select a percentage of the original size. If you’ve got some footage in 4K but some in 1080p, 25% of 4K is a usable 960x540, but 1080p ends up a very blurry 480x270.

We want to produce file names based on the originals, so this script will match the folder structure that was there initially. Copy the files from all the cameras into their own folders - one for each camera.

footage/cam1/video1.MP4
footage/cam1/video2.MP4
footage/cam2/video1.MP4
footage/cam2/video2.MP4

./make_proxies.sh footage/ will give you a directory structure like this:

footage/cam1/video1.MP4
footage/cam1/video2.MP4
footage/cam2/video1.MP4
footage/cam2/video2.MP4
footage/proxy/cam1/video1.mov
footage/proxy/cam1/video2.mov
footage/proxy/cam2/video1.mov
footage/proxy/cam2/video2.mov

After importing the original footage into Final Cut Pro, we can attempt to “re”-link the footage to the proxy media. Select all the clips, then go to File -> Relink Files -> Proxy Media.... Then select the proxy folder and… none of the files are accepted. While you can select an individual clip, and manually match it up with the corresponding proxy media, it won’t automatically match the lot.

So the first thing we can try is the dodgy solution: rename one of the .mov files to .mp4. After this, it’ll automatically match the clip but you’ll see an error that the proxy has no audio track but the original does. Experimentation by opening the file in Quicktime Player reveals that the clips do open as .mp4 but the container format is incorrectly detected and the audio track is missing.

The next thing we could try is symlinking the .mov file to the corresponding file in .mp4. However, symlinks are resolved at a filesystem level, and Final Cut Pro / Quicktime know no different. The same problem as above persists.

But there’s something else we can do. Mac OS wasn’t always UNIX based, and there also exists something from Classic MacOS that’s like a symlink… but not one - the alias. Aliases are different - they’re actual files, which have additional metadata saying they’re an alias. They store the path of their target in addition to filesystem data that lets the target be located if the target is moved and no file exists at the given path. They’re resolved at an application level, and the application reads the alias then opens the new file.

Note here that we opened the alias, yet the QuickTime Player title bar shows the original filename.

But there’s one small difficulty - there’s no commandline application to create an alias (ok, there’s mkalias in osxutils - it didn’t work when I tried it). Searching everyone’s favourite Q&A site (no relation - that site didn’t exist when I registered this domain!) reveals a lot of people who either don’t know that there’s a difference, think the questioner means a bash command alias, or mistake everything for an XY problem saying that the questioner doesn’t need an alias and should use a symlink instead.

In a similar method to above, we create alias_proxies.sh:

#!/bin/bash
set -euo pipefail

find "$1" -iname *.mov -print0 | xargs -0 -n 1 $(dirname $0)/make_alias.sh

Then we make a script - make_alias.sh to do the actual aliasing - making use of the osascript command line tool. We interpolate some bash variables into some AppleScript and get the Finder to do our dirty work.

#!/bin/bash
set -euo pipefail

ALIAS_DIRNAME=$(dirname -- "$1")
ALIAS_FILENAME=$(basename -- "$1")

echo -n "Creating alias for $ALIAS_FILENAME: "

ALIAS_FILENAME_MP4=${ALIAS_FILENAME%.mov}.MP4

/usr/bin/osascript <<EOF
tell application "Finder"
	make new alias to file (POSIX file "$1") at (POSIX file "$ALIAS_DIRNAME")
	set name of result to "$ALIAS_FILENAME_MP4"
end tell
EOF

Turns out this isn’t particularly well documented, and it seems the at parameter actually wants a directory (posix file? everything’s a file, even directories). This script also assumes all the original files were .MP4 - change the ALIAS_FILENAME_MP4 line if that’s not true.

Run ./alias_proxies.sh footage from the Mac side, and it’ll run through creating all the aliases. Go back to Final Cut Pro, go to File -> Relink Files -> Proxy Media..., select the proxy folder, and all the media is successfully linked.

I haven’t done the final export yet, but I don’t forsee any issues. I’ll update here once it’s done. Final Cut Pro seems to be happy with the proxy media so far, though…

Next up: tweaking FFmpeg settings for faster encoding - does using nvdec speed things up on the H.265 decode side? Is ffmpeg_aw or ffmpeg_ks faster? Can we set FFmpeg to use less threads but process more clips at once for greater throughput?


Share