How to Create an FFmpeg Watch Folder for Transcoding

This guide explains how to set up an automated watch folder system that detects newly added media files and automatically transcodes them using FFmpeg. By implementing an event-driven automation script, you can streamline your video processing workflow, eliminate manual command-line encoding, and ensure that converted media is automatically organized into an output directory.

Prerequisites

Before setting up the script, ensure you have the following installed on your system:

  • FFmpeg: Must be installed and accessible in your system's PATH.
  • Python 3: Used to run the cross-platform watch script.
  • Watchdog Library: A Python library that monitors file system events. Install it via pip:
pip install watchdog

Creating the Automated Watch Folder Script

The most robust way to monitor directories across Windows, macOS, and Linux is using Python with the watchdog library. This method avoids polling overhead and catches file system events as they happen.

Save the following code as ffmpeg_watcher.py:

import os
import time
import subprocess
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

# Configuration
WATCH_DIR = "/path/to/watch_folder"
OUTPUT_DIR = "/path/to/output_folder"
SUPPORTED_EXTENSIONS = ('.mp4', '.mkv', '.mov', '.avi', '.ts')

class TranscodeHandler(FileSystemEventHandler):
    def on_created(self, event):
        if event.is_directory:
            return

        file_path = event.src_path
        file_name, file_ext = os.path.splitext(os.path.basename(file_path))

        if file_ext.lower() in SUPPORTED_EXTENSIONS:
            self.process_file(file_path, file_name)

    def process_file(self, file_path, file_name):
        # Ensure file copy operation has fully finished
        self.wait_for_file_transfer(file_path)

        output_path = os.path.join(OUTPUT_DIR, f"{file_name}_transcoded.mp4")

        # FFmpeg command: H.264 video, AAC audio, faststart enabled for web
        cmd = [
            "ffmpeg",
            "-y",  # Overwrite output if it exists
            "-i", file_path,
            "-c:v", "libx264",
            "-crf", "23",
            "-preset", "medium",
            "-c:a", "aac",
            "-b:a", "128k",
            "-movflags", "+faststart",
            output_path
        ]

        print(f"Started transcoding: {file_path}")
        try:
            subprocess.run(cmd, check=True)
            print(f"Finished transcoding: {output_path}")
            # Optional: Delete or archive the original file
            # os.remove(file_path)
        except subprocess.CalledProcessError as e:
            print(f"Error transcoding {file_path}: {e}")

    def wait_for_file_transfer(self, file_path):
        """Wait until file size remains constant to ensure copying is complete."""
        historical_size = -1
        while True:
            try:
                current_size = os.path.getsize(file_path)
                if current_size == historical_size and current_size > 0:
                    break
                historical_size = current_size
                time.sleep(2)
            except FileNotFoundError:
                time.sleep(1)

if __name__ == "__main__":
    os.makedirs(WATCH_DIR, exist_ok=True)
    os.makedirs(OUTPUT_DIR, exist_ok=True)

    event_handler = TranscodeHandler()
    observer = Observer()
    observer.schedule(event_handler, WATCH_DIR, recursive=False)
    observer.start()

    print(f"Watching directory: {WATCH_DIR}")
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()

How the Script Works

  1. Path Initialization: The script creates both the input (WATCH_DIR) and output (OUTPUT_DIR) folders if they do not already exist.
  2. Event Detection: When a new file is placed in the watch folder, on_created triggers.
  3. Lock Protection: The wait_for_file_transfer function monitors the file size until it stops growing. This prevents FFmpeg from attempting to encode a file while it is still being copied over the network or disk.
  4. Encoding Execution: subprocess.run executes the FFmpeg command using standard H.264 and AAC parameters.
  5. Output Organization: The encoded file is deposited into the output folder with _transcoded.mp4 appended to the name.

Running the Script in the Background

Linux (Systemd Service)

To run the script automatically on system boot:

  1. Create a service file: /etc/systemd/system/ffmpeg-watcher.service
  2. Add the following configuration:
[Unit]
Description=FFmpeg Watch Folder Service
After=network.target

[Service]
Type=simple
User=your_username
ExecStart=/usr/bin/python3 /path/to/ffmpeg_watcher.py
Restart=on-failure

[Install]
WantedBy=multi-user.target
  1. Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable --now ffmpeg-watcher.service

Windows

On Windows, run the script via PowerShell or use a task manager utility such as NSSM (Non-Sucking Service Manager) to run python ffmpeg_watcher.py as a background Windows Service.