Pre and Post-Processing
Pre- and post-processing in FFmate allow you to extend transcoding tasks by running custom scripts before the ffmpeg command starts and after it successfully completes. This powerful feature enables you to automate a wide range of activities, from input validation and file preparation to notifications, archiving, and integration with other systems.
You can define pre and post-processing steps either directly within a task creation request or as part of a Preset. If both define pre-processing, the task's settings replace the preset's settings completely. The same applies to post-processing.
Configuration Parameters
For both pre-processing and post-processing, you can configure the following:
scriptPath[optional] – The command or script FFmate runs. Pre-processing runs it before theffmpegcommand, post-processing after it. It supports wildcards to pass dynamic values like filenames, UUIDs, or dates as arguments to your script.
Example: python3 /opt/ffmate_scripts/prepare_audio.py --input ${INPUT_FILE} --normalize-level -3dBFS
Note:
FFmate will attempt to run the scriptPath as a system command. Make sure the script is executable and the path is correct. It runs with the same environment variables, permissions and working directory as the FFmate process.
Anyone with API access can run commands
scriptPath can be any command, not only the path to a script, for example bash -c "…". Anyone who can create a task, preset or watchfolder can therefore run commands on the computer FFmate runs on. By default, FFmate listens on 0.0.0.0 without a password, so that is anyone on the network.
Restrict who can reach FFmate with --host 127.0.0.1 or --basic-auth and a firewall, and run FFmate as a user without admin or root rights.
No shell
scriptPath is run directly, not through a shell. Shell features such as pipes (|), && or $VARIABLES don't work there. To use them, put them inside your script and call the script.
How Exit Codes Work
When a script finishes running, it returns an exit code — a number that tells ffmate whether it succeeded or failed.
- An exit code of
0means the script completed successfully. - A non-zero exit code means the script encountered an error.
- For pre-processing, if the script fails, the
ffmpegcommand will not run, and the task will be marked as failed. - For post-processing, the
ffmpegcommand will already have completed successfully, but the task will still be marked as failed due to the post-processing error.
- For pre-processing, if the script fails, the
FFmate saves the script's error output and exit code in the task's error field, for example PreProcessing failed: disk full (exit code: 3).
sidecarPath[optional] – Specifies the path where FFmate should write a JSON "sidecar" file containing detailed information about the current task. This path supports wildcards. Your script can then read this file to get full context and make decisions accordingly. If FFmate can't write the sidecar, the script doesn't run and the task fails.importSidecar[optional, pre-processing only] – Iftrue, FFmate reads the sidecar back in after the pre-processing script finishes. Default:false. See Importing a Task’s Sidecar.What’s in the sidecar file?
The sidecar JSON contains a snapshot of the task at the time the script runs. Empty properties are left out.- For pre-processing, this includes the raw input and output paths and command, task metadata, UUID, name, priority, and more. Wildcards in these aren't resolved yet.
Example:
json{ "uuid": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "name": "My Epic Movie.mov", "command": { "raw": "-i ${INPUT_FILE} -c:v libx264 -preset fast ${OUTPUT_FILE}" }, "inputFile": { "raw": "/watch/My Epic Movie.mov" }, "outputFile": { "raw": "/output/${INPUT_FILE_BASENAME}.mp4" }, "metadata": { "show": "My Awesome Show", "season": 2, "episode": 5 }, "status": "PRE_PROCESSING", "progress": 0, "remaining": 0, "priority": 5, "source": "watchfolder", "retries": 0, "retried": 0, "preProcessing": { "scriptPath": { "raw": "/scripts/preprocess.sh ${INPUT_FILE}" }, "sidecarPath": { "raw": "/temp/${INPUT_FILE_BASENAME}.json", "resolved": "/temp/My Epic Movie.json" }, "importSidecar": true, "startedAt": 1735689600000 }, "startedAt": 1735689600000, "createdAt": 1735689590000, "updatedAt": 1735689600000 }- For post-processing, it also includes the resolved command, input file and output file. The task status at this point is
POST_PROCESSING. It changes toDONE_SUCCESSFULonce the post-processing script completes without errors.
Importing a Task’s Sidecar
When the property importSidecar is set to true, FFmate will re-import the sidecar JSON after your pre-processing script finishes.
The flow is simple:
- When the task starts, FFmate writes the task’s sidecar JSON to the path you configured in
preProcessing.sidecarPath, then runs your pre-processing script. - Inside your script, you can read and modify the sidecar JSON programmatically.
- When the script exits, FFmate re-imports the updated sidecar and continues processing the task with those updates.
This way, the pre-processing script can change how FFmate processes the task. For example, it can:
- run tools like
ffprobeorMediaInfoand inject the results into themetadataobject. - adjust the task’s command, input file or output file based on resolution, bitrate, or aspect ratio. Change the
rawvalue; FFmate resolves the wildcards after the import.
⚠️ Important:
Only change the values of existing properties, and keep each value's type. For example,prioritymust stay a number.
A value with the wrong type makes the FFmate task fail.
Workflow
This section outlines how FFmate runs a task, showing where pre- and post-processing scripts fit, how wildcards are resolved, where the sidecar import happens, and how errors are handled.
- Task Queued — A new task is created (directly or via a watchfolder).
- Pre-Processing (if defined)
- FFmate resolves wildcards in
sidecarPath(if defined) and writes the task sidecar JSON. If the sidecar can't be written, the task status is set toDONE_ERRORand processing stops. - FFmate resolves wildcards in
scriptPath. - FFmate executes the pre-processing script.
- If the script fails (non-zero exit code), the task status is set to
DONE_ERRORand processing stops. The script error is saved in the task'serrorfield. - If
importSidecaris true: FFmate re-imports the updated sidecar JSON after the script finishes. If re-import fails, the task is set toDONE_ERROR.
- FFmate resolves wildcards in
- FFmpeg Processing
- If pre-processing was successful (or not defined), FFmate resolves wildcards for the main command, input file, and output file.
- FFmate creates the output file's folder if it doesn't exist.
- FFmate executes the FFmpeg command.
- If FFmpeg fails, the task status is set to
DONE_ERRORand processing stops. Post-processing will not run.
- Post-Processing (if defined)
- Assuming FFmpeg completed successfully, FFmate resolves wildcards in
sidecarPath(if defined) and writes the task sidecar JSON (now including final output paths). - FFmate resolves wildcards in
scriptPath. - FFmate executes the post-processing script.
- If the script fails (non-zero exit code), the task status is set to
DONE_ERROR. The script error is saved in the task'serrorfield.
- Assuming FFmpeg completed successfully, FFmate resolves wildcards in
- Task Completion
- If post-processing was successful (or not defined), the task status is set to
DONE_SUCCESSFUL.
- If post-processing was successful (or not defined), the task status is set to
If a task is canceled while a pre- or post-processing script is running, the script runs to the end. The task status changes to DONE_CANCELED right away and stays that way. When a task is canceled during pre-processing, FFmpeg doesn't run.
Examples
Post-Processing – Upload to Cloud Storage and Notify
Once transcoding completes successfully, upload the output file to an S3 bucket and send a Slack notification to keep your team informed.
Example:
This example shows how post-processing can be configured to run a custom script after a successful ffmpeg transcode, while also generating a sidecar JSON file containing task details.
{
"postProcessing": {
"scriptPath": "/opt/ffmate_scripts/upload_and_notify.sh ${OUTPUT_FILE_DIR}/${OUTPUT_FILE_BASENAME}.post_task_info.json",
"sidecarPath": "${OUTPUT_FILE_DIR}/${OUTPUT_FILE_BASENAME}.post_task_info.json"
}
// ... other preset/task details
}upload_and_notify.sh(Conceptual):bash#!/bin/bash set -e # Exit immediately if a command exits with a non-zero status. # FFmate passes the sidecar path as the first argument (see scriptPath above) SIDECAR_FILE="$1" if [ -z "$SIDECAR_FILE" ] || [ ! -f "$SIDECAR_FILE" ]; then echo "Error: Sidecar file path not provided or file not found." >&2 exit 1 fi # Read data from sidecar using 'jq' (JSON processor) OUTPUT_FILE=$(jq -r '.outputFile.resolved' "$SIDECAR_FILE") TASK_NAME=$(jq -r '.name // "Untitled Task"' "$SIDECAR_FILE") TASK_UUID=$(jq -r '.uuid' "$SIDECAR_FILE") if [ -z "$OUTPUT_FILE" ]; then echo "Error: Could not extract output file from sidecar." >&2 exit 1 fi S3_BUCKET="s3://my-ffmate-outputs" SLACK_WEBHOOK_URL="https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK" echo "Uploading ${OUTPUT_FILE} to ${S3_BUCKET}..." if ! aws s3 cp "${OUTPUT_FILE}" "${S3_BUCKET}/"; then echo "Error: S3 upload failed for ${OUTPUT_FILE}." >&2 exit 2 fi echo "Upload successful." # Send Slack notification MESSAGE_TEXT="Task Complete: '${TASK_NAME}' (UUID: ${TASK_UUID}). Output: ${S3_BUCKET}/$(basename "${OUTPUT_FILE}")" PAYLOAD="{\"text\": \"${MESSAGE_TEXT}\"}" if ! curl -X POST -H 'Content-type: application/json' --data "${PAYLOAD}" "${SLACK_WEBHOOK_URL}"; then echo "Warning: Slack notification failed, but file was uploaded." >&2 # Decide if this should be a hard fail (exit 3) or just a warning fi echo "Post-processing complete for ${TASK_UUID}." exit 0 # Success