FFmpeg: Improved 'Rainbow-Trail' effect





* Includes sound 🔊

I have updated the script for this FFmpeg 'rainbow' effect I created in 2017¹ as there were numerous flaws, errors, and inadequacies in that earlier version. One major issue was the inability to colorkey with any colour other than black; this has been resolved.

This time, the effect is based on the 'extractplanes' filter and the alpha levels created after using a 'colorkey' filter. This produces a much more refined result; better colour shaping, and maintains most of the original foreground subject. This 'extractplanes' filter can even be removed from the filtergraph, to create an alternative, more subtle effect.


#!/usr/bin/env bash

# Generate rainbow-trail video effect with FFmpeg.

# Parameters:
# $1: Filename
# $2: Colorkey value [default: 0000FF]
# $3: Colorkey similarity value [default: 0.3]
# $4: Colorkey blend value [default: 0.1]
# $5: Number of colour iterations [default: 7]
# $6: Delay between colours [default: 0.1]
# $7: Alpha plane extraction [default: true]

# version: 2020.07.20_13.54.28
#    - This version uses alpha plane extraction to significantly improve quality
#      and allows for correct keying with colours other than black. Alpha plane
#      extraction can be disabled for an alternative effect.
#    - Created & tested with FFmpeg 4.3.1, GNU Bash 4.4.2, and Ubuntu MATE 18.04

# source: http://oioiiooixiii.blogspot.com

function rainbowFilter()
{
   local iterations="${4:-7}"    # Number of colour layers
   local delay="${5:-0.1}"       # Delay between appearance of each colour layer
   local ptsDelay=0              # Tally for delay between colour layers
   local key="0x${1:-0000FF}"    # Colorkey colour
   local colorSim="${2:-0.3}"    # Colorkey similarity level
   local colorBlend="${3:-0.1}"  # Colorkey blending level
   local filtergraph=""          # Used to store for-loop generated filterchains
                                 # Sets the state of the extractplanes filter
   [[ "$6" == "false" ]] \
   && local extractFilter="null" || local extractFilter="extractplanes=a"

   declare -a colours                    # Array of colours
   colours+=("2:0:0:0:0:0:0:0:2:0:0:0")  # Violet
   colours+=(".5:0:0:0:0:0:0:0:2:0:0:0") # Indigo
   colours+=("0:0:0:0:0:0:0:0:2:0:0:0")  # Blue
   colours+=("0:0:0:0:2:0:0:0:0:0:0:0")  # Green
   colours+=("2:0:0:0:2:0:0:0:0:0:0:0")  # Yellow
   colours+=("2:0:0:0:.5:0:0:0:0:0:0:0") # Orange
   colours+=("2:0:0:0:0:0:0:0:0:0:0:0")  # Red

   # Build colour layers part of filtergraph
   for (( i=0;i<${iterations};i++ ))
   {
      # 'bc' command used for floating point addition
      ptsDelay="$(bc <<<"${ptsDelay}+${delay}")"
      filtergraph="[original]split[original][top];
                   [top]colorkey=${key}:${colorSim}:${colorBlend},
                        ${extractFilter},
                        colorchannelmixer=${colours[$((i%7))]},
                        setpts=PTS+$ptsDelay/TB,
                        chromakey=black:0.01:0.1[top];
                   [bottom][top]overlay[bottom];
                   ${filtergraph}"
   }

   # Return full filtergraph, with necessary prefix and suffix filterchains
   printf '%s%s%s' "colorkey=${key}:${colorSim}:${colorBlend},
                    split[original][bottom];
                    [bottom]colorchannelmixer=0:0:0:0:0:0:0:0:0:0:0:0[bottom];"\
                   "${filtergraph}"\
                   "[bottom][original]overlay"
}

# Alter/replace FFmpeg command to desired specification
ffmpeg -i "$1" -vf "$(rainbowFilter "${@:2}")" -crf 10 "${1}_rainbow.mkv"
download: ffmpeg-rainbow-2020.sh

I have rewritten some parts of the script specifically to make things a bit clearer, which I hope is the case. In this vein, I have also included an alternative filtergraph, which lays out the basic process, without need for a for-loop.

violet="colorchannelmixer=2:0:0:0:0:0:0:0:2:0:0:0"
indigo="colorchannelmixer=.5:0:0:0:0:0:0:0:2:0:0:0"
blue="colorchannelmixer=0:0:0:0:0:0:0:0:2:0:0:0"
green="colorchannelmixer=0:0:0:0:2:0:0:0:0:0:0:0"
yellow="colorchannelmixer=2:0:0:0:2:0:0:0:0:0:0:0"
orange="colorchannelmixer=2:0:0:0:.5:0:0:0:0:0:0:0"
red="colorchannelmixer=2:0:0:0:0:0:0:0:0:0:0:0"

ffmpeg -i video.mkv -vf "
   split [a][b];
   [b]colorkey=0x0000FF:0.3:0.1,
      extractplanes=a,split=7[b1][b2][b3][b4][b5][b6][b7];
   [b1]${red},setpts=PTS+0.7/TB[b1];
   [b2]${orange},setpts=PTS+0.6/TB,chromakey=black:0.01:0.1[b2];
   [b1][b2]overlay[b1];
   [b3]${yellow},setpts=PTS+0.5/TB,chromakey=black:0.01:0.1[b3];
   [b1][b3]overlay[b1];
   [b4]${green},setpts=PTS+0.4/TB,chromakey=black:0.01:0.1[b4];
   [b1][b4]overlay[b1];
   [b5]${blue},setpts=PTS+0.3/TB,chromakey=black:0.01:0.1[b5];
   [b1][b5]overlay[b1];
   [b6]${indigo},setpts=PTS+0.2/TB,chromakey=black:0.01:0.1[b6];
   [b1][b6]overlay[b1];
   [b7]${violet},setpts=PTS+0.1/TB,chromakey=black:0.01:0.1[b7];
   [b1][b7]overlay[b1];
   [a]colorkey=0x0000FF:0.4:0.1[a];
   [b1][a]overlay" \
output.mkv

As demonstrated in the example videos, the filtergraph produces an effect with a transparent background, which allows it to be used on top of other sources, or rendered in a video codec that allows transparency. The script arguments for each quadrant (top left; clockwise):

source video
ffmpeg-rainbow-2020.sh video.mkv "" "" "" "14"
ffmpeg-rainbow-2020.sh video.mkv
ffmpeg-rainbow-2020.sh video.mkv "" "" "" "28" "0.07" "false"

[ 2020-09-01 | video removed ]

¹ original 2017 version: https://oioiiooixiii.blogspot.com/2017/09/ffmpeg-rainbow-trail-chromakey-effect.html
'extractplanes' documentation: https://ffmpeg.org/ffmpeg-filters.html#extractplanes
motivation: https://twitter.com/av_morgan/status/1283630773308809217
source video (Fanny - "Ain't that peculiar" (Beat-Club, 1972)): https://www.youtube.com/watch?v=imZUqkPlUaQ