Skip to content

Instantly share code, notes, and snippets.

@zmwangx
Last active March 3, 2024 15:51
  • Star 20 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save zmwangx/11275000 to your computer and use it in GitHub Desktop.
Script to generate thumbnails of a video and combine into one tile image. Uses FFmpeg and ImageMagick.
#!/bin/zsh
# Script to generate thumbnails of a video and combine into one tile image.
#
# CAUTION:
# FFprobe might fail to extract duration info from a MKV container. Use
# ffmpeg -i video.mkv -c:v copy -c:a copy video.mp4
# to swap to an MP4 container.
#
# Dependencies:
# 1. ffmpeg
# 2. ImageMagick
#
# Usage:
# ./thumbnails.sh NFRAMES TILE SIZE INPUT OUTPUT
#
# NFRAMES is the number of frames (thumbnails) to generate, e.g., 16;
# TILE is in the form "MxN" (where M * N should match NFRAMES), e.g., 4x4;
# SIZE is the length of the longer side of the output, e.g., 1920 if you want
# to get an 1920*1080 output image;
# INPUT is the path to the input file;
# OUTPUT is the path to the output file (make sure intermediate directories
# exist).
#
# Example:
# ./thumbnail.sh 16 4x4 1920 video.mp4 thumbnails.png
#
# Credit:
# http://goo.gl/vzXW1b (FFmpeg wiki: generate thumbnails)
# http://stackoverflow.com/q/7395343 (extract video length)
# http://apple.stackexchange.com/q/52879 (combine images)
if [[ $# != 5 ]]; then
echo "wrong number of arguments
Usage:
./thumbnails.sh NFRAMES TILE SIZE INPUT OUTPUT
NFRAMES is the number of frames (thumbnails) to generate, e.g., 16;
TILE is in the form 'MxN' (where M * N should match NFRAMES), e.g., 4x4;
SIZE is the length of the longer side of the output, e.g., 1920 if you want
to get an 1920*1080 output image;
INPUT is the path to the input file;
OUTPUT is the path to the output file (make sure intermediate directories
exist).
Example:
./thumbnail.sh 16 4x4 1920 video.mp4 thumbnails.png
"
return 1
fi
NFRAMES=$1
TILE=$2
SIZE=$3
INPUT=$4
OUTPUT=$5
DURATION=$(ffprobe -loglevel error -show_streams $INPUT | grep duration= | cut -f2 -d= | head -1)
FPS=$(echo "$NFRAMES / $DURATION" | bc -l)
OFFSET=$(echo "$DURATION / $NFRAMES / 2" | bc -l)
# generate thumbnails in the /tmp folder
TMPDIR=/tmp/thumbnails-${RANDOM}/
mkdir $TMPDIR
ffmpeg -ss $OFFSET -i $INPUT -f image2 -vf fps=fps=$FPS ${TMPDIR}thumb%04d.jpg
montage ${TMPDIR}thumb*.jpg -background white -geometry +5+5 -tile $TILE ${TMPDIR}output.jpg
convert ${TMPDIR}output.jpg -resize ${SIZE}x${SIZE} $OUTPUT
@Syl0pt
Copy link

Syl0pt commented Sep 21, 2017

Hi, i've downloaded this and am very interested in using it, but I have no idea how to. I tried moving a video file into the same folder as thumbnails.sh and double clicking the .sh but nothing happens. i've tried opening git bash into the folder and using ur example: ./thumbnail.sh 16 4x4 1920 video.mp4 thumbnails.png with the right info filled in, but also nothing happens. even tried using it in the cmd, but I didn't get anywhere. Obviously I don't know what i'm doing here, lol.

Is it possible to help a beginner out? Or at least point me in the right direction. Thank you.

Copy link

ghost commented Mar 17, 2018

@Syl0pt
Your command is correct. Make sure you have ffmpeg and imagemagick installed (listed as "Dependencies" above).
Ubuntu: sudo apt-get install ffmpeg imagemagick
macOS: install Homebrew, then run brew install ffmpeg imagemagick

then the gist should work as described. (tested on mac)

@kybernetyk
Copy link

this is the best script to generate thumbnail collages from video files. thanks!

@ashleyconnor
Copy link

Swapped:
DURATION=$(ffprobe -loglevel error -show_streams $INPUT | grep duration= | cut -f2 -d= | head -1)

For:
DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $INPUT)

@romanwarlock
Copy link

romanwarlock commented Aug 10, 2019

First of all great job.
Script works for mkv, by calculating duration in seconds with ffmpeg -i (one long line):
DURATION=$(ffmpeg -i "$4" 2>&1 | grep "Duration"| cut -d ' ' -f 4 | sed s/,// | sed 's@\..*@@g' | awk '{ split($1, A, ":"); split(A[3], B, "."); print 3600*A[1] + 60*A[2] + B[1] }')

I chose another approach in script, by making snapshots ffmpeg ... -vframes 1 ... ...
This way is really faster for small amount of thumbnails.
I debugged approach to using the script parameters, in the origianl script, space in filename is a problem: see how use "$4" in DURATION=....
I changed usage to . th.sh COLUMNS ROWS SIZE "INPUT", thus NFRAMES=$(echo "scale=0;$1*$2" | bc) and TILE=$(echo "$1x$2")
I also added more stuff from imagemagick to put info about video (name, resolution, size, duration), though I had to edit /etc/ImageMagick-6/policy.xml by commenting all the policy to produce thumbnails

@romanwarlock
Copy link

romanwarlock commented Dec 8, 2020

I'm having day off so I've removed all dependencies from ImageMagick: combining images, making white backgroud and adding text using only ffmpeg: https://github.com/romanwarlock/thumbnails/blob/master/thumbgen.sh

@romanwarlock
Copy link

romanwarlock commented Jul 20, 2022

It turned out existing bc dependency NFRAMES=$(echo "scale=0;$X*$Y" | bc) which I removed by using awk: NFRAMES=echo | awk '{print (X*Y)}' X="$X" Y="$Y}}"``. Github doesn't show all the symbols correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment