Create Short Preview from Video

By  on  

I reference an adult video (porn) site in this post.  We're all grown ups, please do not be offended.

Displaying a preview image before loading a video is common practice with web media these days; oftentimes that image preview is the video's first frame or an important frame from the video.  If you've visited an adult site recently (ahem), you will have noticed that not only do you get a preview image but hovering over said image shows a small sample video with snippets at different points of the movie.  Naturally I was interested in how all of this worked and concluded the following:

  • They create the preview image using a tool like ffmpeg
  • Upon hover of image, the preview video is loaded asynchronously in the background
  • Once the preview image has loaded, the video is displayed over the image and autoplayed
  • When the mouse leaves, the video is hidden (in case the user hovers over the image again)

Achieving the <image> and <video> element swap is simple but I wanted to know PornHub, the largest adult site network, generated their preview videos.  The idea behind the preview video is simple, of course, but I wanted to teach myself more about ffmpeg and bash scripting.  Here's the proof:

Video preview generated from a local download of Kung Fury.

Let's deconstruct my code to learn how this (very basic) preview video can be created!

Running the Script

Our script, preview.sh, will be called as follows:

# ./preview.sh {input.format} {output.format}
$ ./preview.sh kung-fury.mp4 kung-fury-preview.mp4

In a perfect world the script would accept loads more arguments, like desired video size or any of the settings we'll assume within the script, but I wanted to focus on getting the meat of the script working; adding command line arguments would be a routine exercise when everything else was a go.

The Script: preview.sh

The script starts with some very basic validation:

sourcefile=$1
destfile=$2

# Overly simple validation
if [ ! -e "$sourcefile" ]; then
  echo 'Please provide an existing input file.'
  exit
fi

if [ "$destfile" == "" ]; then
  echo 'Please provide an output preview file name.'
  exit
fi

Next we have a series of variables relating to video length, desired points in the video to cut previews, the video size, etc.:

# Detect destination file extension
extension=${destfile#*.}

# Get video length in seconds
length=$(ffprobe $sourcefile  -show_format 2>&1 | sed -n 's/duration=//p' | awk '{print int($0)}')

# Start 20 seconds into the video to avoid opening credits (arbitrary)
starttimeseconds=20

# Mini-snippets will be 2 seconds in length
snippetlengthinseconds=2

# We'll aim for 5 snippets spread throughout the video
desiredsnippets=5

# Ensure the video is long enough to even bother previewing
minlength=$(($snippetlengthinseconds*$desiredsnippets))

# Video dimensions (these could probably be command line arguments)
dimensions=640:-1

# Temporary directory and text file where we'll store snippets
# These will be cleaned up and removed when the preview image is generated
tempdir=snippets
listfile=list.txt

If the video is too short to generate a preview from, simply bail:

# Display and check video length
echo 'Video length: ' $length
if [ "$length" -lt "$minlength" ]
then
  echo 'Video is too short.  Exiting.'
  exit
fi

Next we generate a series of "snippet" videos at calculated times within the video, saving these snippet videos to a temporary directory:

# Loop and generate video snippets
mkdir $tempdir
interval=$(($length/$desiredsnippets-$starttimeseconds))
for i in $(seq 1 $desiredsnippets)
  do
    # Format the second marks into hh:mm:ss format
    start=$(($(($i*$interval))+$starttimeseconds))
    formattedstart=$(printf "%02d:%02d:%02d\n" $(($start/3600)) $(($start%3600/60)) $(($start%60)))
    echo 'Generating preview part ' $i $formattedstart
    # Generate the snippet at calculated time
    ffmpeg -i $sourcefile -vf scale=$dimensions -preset fast -qmin 1 -qmax 1 -ss $formattedstart -t $snippetlengthinseconds $tempdir/$i.$extension &>/dev/null
done

Note: copying and slicing videos with ffmpeg is difficult if you aren't a codec and media expert, which I certainly am not.  These basic settings do allow the job to get done but maybe not in the most optimal way.  Please comment below if you know a way to improve performance or video quality.

The last step is concatenating the snippet videos in to the final preview video:

# Concat videos
echo 'Generating final preview file'

# Generate a text file with one snippet video location per line
# (https://trac.ffmpeg.org/wiki/Concatenate)
for f in $tempdir/*; do echo "file '$f'" >> $listfile; done

# Concatenate the files based on the generated list
ffmpeg -f concat -safe 0 -i $listfile -c copy $destfile &>/dev/null

echo 'Done!  Check ' $destfile '!'

# Cleanup
rm -rf $tempdir $listfile

Video concatenation is easy in this case because we're concatenating snippets from the same original video.  Concatenation would be much more difficult if we were using videos of different frame rates, sizes, etc.

The final script in all its glory:

sourcefile=$1
destfile=$2

# Overly simple validation
if [ ! -e "$sourcefile" ]; then
  echo 'Please provide an existing input file.'
  exit
fi

if [ "$destfile" == "" ]; then
  echo 'Please provide an output preview file name.'
  exit
fi

# Detect destination file extension
extension=${destfile#*.}

# Get video length in seconds
length=$(ffprobe $sourcefile  -show_format 2>&1 | sed -n 's/duration=//p' | awk '{print int($0)}')

# Start 20 seconds into the video to avoid opening credits (arbitrary)
starttimeseconds=20

# Mini-snippets will be 2 seconds in length
snippetlengthinseconds=2

# We'll aim for 5 snippets spread throughout the video
desiredsnippets=5

# Ensure the video is long enough to even bother previewing
minlength=$(($snippetlengthinseconds*$desiredsnippets))

# Video dimensions (these could probably be command line arguments)
dimensions=640:-1

# Temporary directory and text file where we'll store snippets
# These will be cleaned up and removed when the preview image is generated
tempdir=snippets
listfile=list.txt

# Display and check video length
echo 'Video length: ' $length
if [ "$length" -lt "$minlength" ]
then
  echo 'Video is too short.  Exiting.'
  exit
fi

# Loop and generate video snippets
mkdir $tempdir
interval=$(($length/$desiredsnippets-$starttimeseconds))
for i in $(seq 1 $desiredsnippets)
  do
    # Format the second marks into hh:mm:ss format
    start=$(($(($i*$interval))+$starttimeseconds))
    formattedstart=$(printf "%02d:%02d:%02d\n" $(($start/3600)) $(($start%3600/60)) $(($start%60)))
    echo 'Generating preview part ' $i $formattedstart
    # Generate the snippet at calculated time
    ffmpeg -i $sourcefile -vf scale=$dimensions -preset fast -qmin 1 -qmax 1 -ss $formattedstart -t $snippetlengthinseconds $tempdir/$i.$extension &>/dev/null
done

# Concat videos
echo 'Generating final preview file'

# Generate a text file with one snippet video location per line
# (https://trac.ffmpeg.org/wiki/Concatenate)
for f in $tempdir/*; do echo "file '$f'" >> $listfile; done

# Concatenate the files based on the generated list
ffmpeg -f concat -safe 0 -i $listfile -c copy $destfile &>/dev/null

echo 'Done!  Check ' $destfile '!'

# Cleanup
rm -rf $tempdir $listfile

Improvements

You can bet that PornHub and every other video site that employs this technique has a script much more secure and powerful than this script.  A few ways this script could be improved:

  • Validation:  I check that the file exists but there's no check to ensure it's a valid media file.
  • Command Line Arguments:  Settings like video size should probably be an optional command line argument.
  • Encoding:  I know that additionally command line arguments need to be passed in depending on video file type (and there are a billion out there).  A serious script would really beef of that logic.
  • Optimization:  I'm not even close to a video or media expert of any kind.  There has to be a way to speed up the snippet generation process as it's quite slow within the script now.  I usually add -c copy when I slice files but ffmpeg yelled at me so I passed on that argument.  Please share if you know how to improve the slicing or concatenation process!
  • Dependencies:  Not necessarily and improvement but it's important to call out that a simple brew install ffmpeg wont get you many of the codec and encoding support your need.  I needed to do the following to work with webm's, for example:
    brew reinstall ffmpeg --with-fdk-aac --with-ffplay --with-freetype --with-frei0r --with-libass --with-libvo-aacenc --with-libvorbis --with-libvpx --with-opencore-amr --with-openjpeg --with-opus --with-rtmpdump --with-speex --with-theora --with-tools --with-libvorbis
    

Taking the time to piece together this script was exhilarating; a mix of media handling, bash scripting, and seeing the end product for something that's used on scores of video sites makes for a real feeling of achievement.  Please let me know how the script above can be improved!

Recent Features

  • By
    Creating Scrolling Parallax Effects with CSS

    Introduction For quite a long time now websites with the so called "parallax" effect have been really popular. In case you have not heard of this effect, it basically includes different layers of images that are moving in different directions or with different speed. This leads to a...

  • By
    5 Ways that CSS and JavaScript Interact That You May Not Know About

    CSS and JavaScript:  the lines seemingly get blurred by each browser release.  They have always done a very different job but in the end they are both front-end technologies so they need do need to work closely.  We have our .js files and our .css, but...

Incredible Demos

  • By
    MooTools CountDown Plugin

    There are numerous websites around the internet, RapidShare for example, that make you wait an allotted amount of time before presenting you with your reward. Using MooTools, I've created a CountDown plugin that allows you to easily implement a similar system. The MooTools JavaScript The CountDown class...

  • By
    MooTools 1.2 OpenLinks Plugin

    I often incorporate tools into my customers' websites that allow them to have some control over the content on their website. When doing so, I offer some tips to my clients to help them keep their website in good shape. One of the tips...

Discussion

  1. hatoro

    As always thanks for sharing your thoughts. I am interested in the details of said and element swap. Surely it involves async loading of video onhover? I would use webpack, what about you?

  2. John Brown

    “We’re all grown ups” Absolutely wrong! I’m in my early 17’s. Still visiting this site. Moreover if we grown up so it means we seen porn or gave reference from those nasty sites? ridiculus logic.

  3. Awesome! Thanks for the post + script. :) I’m interested in writing a script to pull a poster image out of a webm or mp4 file in order to automate the process a little. Just started scripting with ffmpeg. I’ll checkout your “Create an Image Preview from a Video” post now.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!