Skip to content

Instantly share code, notes, and snippets.

@martinsik
Last active March 11, 2025 00:17
Show Gist options
  • Save martinsik/5237977560ea7d60ad11 to your computer and use it in GitHub Desktop.
Save martinsik/5237977560ea7d60ad11 to your computer and use it in GitHub Desktop.

Revisions

  1. martinsik revised this gist Aug 15, 2014. No changes.
  2. martinsik revised this gist Aug 15, 2014. 2 changed files with 5 additions and 2 deletions.
    5 changes: 4 additions & 1 deletion video_preview.html
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,8 @@
    <!--
    Tutorial code for: http://www.binpress.com/tutorial/generating-nice-movie-previews-with-ffmpeg/138
    -->
    <!DOCTYPE html>
    <html lang="en"> <!-- Set this to the main language of your site -->
    <html lang="en">
    <head>
    <title></title>
    </head>
    2 changes: 1 addition & 1 deletion video_preview.sh
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    #!/bin/bash
    # Bash script that generates film strip video preview using ffmpeg
    # You can see live demo: http://jsfiddle.net/r6wz0nz6/2/
    # Tutorial on Binpress.com: #
    # Tutorial on Binpress.com: http://www.binpress.com/tutorial/generating-nice-movie-previews-with-ffmpeg/138

    if [ -z "$1" ]; then
    echo "usage: ./movie_preview.sh VIDEO [HEIGHT=120] [COLS=100] [ROWS=1] [OUTPUT]"
  3. martinsik revised this gist Aug 15, 2014. 1 changed file with 12 additions and 4 deletions.
    16 changes: 12 additions & 4 deletions video_preview.sh
    Original file line number Diff line number Diff line change
    @@ -1,21 +1,22 @@
    #!/bin/bash
    # Bash script that generates film strip video preview using ffmpeg
    # You can see live demo: http://jsfiddle.net/r6wz0nz6/2/
    # Tutorial on Binpress.com: #

    if [ -z "$1" ]; then
    echo "usage: ./movie_preview.sh VIDEO [HEIGHT=120] [COLS=100] [ROWS=1]"
    echo "usage: ./movie_preview.sh VIDEO [HEIGHT=120] [COLS=100] [ROWS=1] [OUTPUT]"
    exit
    fi

    MOVIE=$1
    # get video name without the path and extension
    MOVIE_NAME=`basename $MOVIE`
    OUT_FILENAME=`echo ${MOVIE_NAME%.*}_preview.jpg`
    OUT_DIR=`pwd`
    OUT_FILEPATH=`echo $OUT_DIR/$OUT_FILENAME`

    HEIGHT=$2
    COLS=$3
    ROWS=$4
    OUT_FILENAME=$5

    if [ -z "$HEIGHT" ]; then
    HEIGHT=120
    @@ -26,6 +27,11 @@ fi
    if [ -z "$ROWS" ]; then
    ROWS=1
    fi
    if [ -z "$OUT_FILENAME" ]; then
    OUT_FILENAME=`echo ${MOVIE_NAME%.*}_preview.jpg`
    fi

    OUT_FILEPATH=`echo $OUT_DIR/$OUT_FILENAME`

    TOTAL_IMAGES=`echo "$COLS*$ROWS" | bc`

    @@ -72,6 +78,8 @@ FFMPEG_CMD="ffmpeg -loglevel panic -i \"$MOVIE\" -y -frames 1 -q:v 1 -vf \"selec
    # # `tile=${COLS}x${ROWS}` Layout captured frames into this grid

    # print enire command for debugging purposes
    echo $FFMPEG_CMD
    # echo $FFMPEG_CMD

    echo $OUT_FILEPATH

    eval $FFMPEG_CMD
  4. martinsik created this gist Aug 15, 2014.
    83 changes: 83 additions & 0 deletions video_preview.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,83 @@
    <!DOCTYPE html>
    <html lang="en"> <!-- Set this to the main language of your site -->
    <head>
    <title></title>
    </head>
    <body>
    <a href="https://www.youtube.com/watch?v=v1uyQZNg2vE" target="_blank" class="video-preview" data-frames="100" data-source="http://i.imgur.com/BX0pV4J.jpg"></a>

    <style>
    body {
    text-align: center;
    padding-top: 20px;
    }
    body > div {/*
    display: inline-block;*/
    }

    .video-preview {
    display: inline-block;
    position: relative;
    background: #ddd;
    overflow: hidden;
    /* This is temporary width and height, these'll be overriden when the source img is loaded. */
    /* If you already know size of a preview frame you can hardcode it here. */
    width: 160px;
    height: 120px;
    border-radius: 3px;
    box-shadow: 0 0 6px #bbb;
    }
    </style>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script>
    (function($) {
    $.fn.videoPreview = function(options) {
    return this.each(function() {
    var elm = $(this);
    var frames = parseFloat(elm.data('frames'));

    var img = $('<img/>', { 'src': elm.data('source') }).hide().css({
    'position': 'absolute',
    'cursor': 'pointer'
    }).appendTo(elm);
    var slider = $('<div/>').hide().css({
    'width': '2px',
    'height': '100%',
    'background': '#ddd',
    'position': 'absolute',
    'z-index': '1',
    'top': '0',
    'opacity': 0.6,
    'cursor': 'pointer'
    }).appendTo(elm);

    var width;

    function defaultPos() {
    img.css('left', -width * frames / 4);
    }

    img.load(function() {
    $(this).show();
    width = this.width / frames;
    elm.css('width', width);
    defaultPos();
    });
    elm.mousemove(function(e) {
    var left = e.clientX - elm.position().left;
    slider.show().css('left', left - 1); // -1 because it's 2px width
    img.css('left', -Math.floor((left / width) * frames) * width);
    }).mouseout(function(e) {
    slider.hide();
    defaultPos();
    });

    });
    };
    })(jQuery);

    $('.video-preview').videoPreview();
    </script>
    </body>
    </html>
    77 changes: 77 additions & 0 deletions video_preview.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,77 @@
    #!/bin/bash
    # Bash script that generates film strip video preview using ffmpeg

    if [ -z "$1" ]; then
    echo "usage: ./movie_preview.sh VIDEO [HEIGHT=120] [COLS=100] [ROWS=1]"
    exit
    fi

    MOVIE=$1
    # get video name without the path and extension
    MOVIE_NAME=`basename $MOVIE`
    OUT_FILENAME=`echo ${MOVIE_NAME%.*}_preview.jpg`
    OUT_DIR=`pwd`
    OUT_FILEPATH=`echo $OUT_DIR/$OUT_FILENAME`

    HEIGHT=$2
    COLS=$3
    ROWS=$4

    if [ -z "$HEIGHT" ]; then
    HEIGHT=120
    fi
    if [ -z "$COLS" ]; then
    COLS=100
    fi
    if [ -z "$ROWS" ]; then
    ROWS=1
    fi

    TOTAL_IMAGES=`echo "$COLS*$ROWS" | bc`

    # get total number of frames in the video
    # ffprobe is fast but not 100% reliable. It might not detect number of frames correctly!
    NB_FRAMES=`ffprobe -show_streams "$MOVIE" 2> /dev/null | grep nb_frames | head -n1 | sed 's/.*=//'`
    # `-show-streams` Show all streams found in the video. Each video has usualy two streams (video and audio).
    # `head -n1` We care only about the video stream which comes first.
    # `sed 's/.*=//'` Grab everything after `=`.

    if [ "$NB_FRAMES" = "N/A" ]; then
    # as a fallback we'll use ffmpeg. This command basically copies this video to /dev/null and it counts
    # frames in the process. It's slower (few seconds usually) than ffprobe but works everytime.
    NB_FRAMES=`ffmpeg -nostats -i "$MOVIE" -vcodec copy -f rawvideo -y /dev/null 2>&1 | grep frame | awk '{split($0,a,"fps")}END{print a[1]}' | sed 's/.*= *//'`
    # I know, that `awk` and `sed` parts look crazy but it has to be like this because ffmpeg can
    # `-nostats` By default, `ffmpeg` prints progress information but that would be immediately caught by `grep`
    # because it would contain word `frame` and therefore output of this entire command would be totally
    # random. `-nostats` forces `ffmpeg` to print just the final result.
    # `-i "$MOVIE"` Input file
    # `-vcodec copy -f rawvideo` We don't want to do any reformating. Force `ffmpeg` to read and write the video as is.
    # `-y /dev/null` Dump read video data. We just want it to count frames we don't care about the data.
    # `awk ...` The line we're interested in has format might look like `frame= 42` or `frame=325`. Because of that
    # extra space we can't just use `awk` to print the first column and we have to cut everything from the
    # beggining of the line to the term `fps` (eg. `frame= 152`).
    # `sed ...` Grab everything after `=` and ignore any spaces
    fi

    # calculate offset between two screenshots, drop the floating point part
    NTH_FRAME=`echo "$NB_FRAMES/$TOTAL_IMAGES" | bc`
    echo "capture every ${NTH_FRAME}th frame out of $NB_FRAMES frames"

    # make sure output dir exists
    mkdir -p $OUT_DIR

    FFMPEG_CMD="ffmpeg -loglevel panic -i \"$MOVIE\" -y -frames 1 -q:v 1 -vf \"select=not(mod(n\,$NTH_FRAME)),scale=-1:${HEIGHT},tile=${COLS}x${ROWS}\" \"$OUT_FILEPATH\""
    # `-loglevel panic` We don’t want to see any output. You can remove this option if you’re having any problem to see what went wrong
    # `-i "$MOVIE"` Input file
    # `-y` Override any existing output file
    # `-frames 1` Tell `ffmpeg` that output from this command is just a single image (one frame).
    # `-q:v 3` Output quality where `0` is the best.
    # `-vf \"select=` That's where all the magic happens. Selector function for [video filter](https://trac.ffmpeg.org/wiki/FilteringGuide).
    # # `not(mod(n\,58))` Select one frame every `58` frames [see the documentation](https://www.ffmpeg.org/ffmpeg-filters.html#Examples-34).
    # # `scale=-1:120` Resize to fit `120px` height, width is adjusted automatically to keep correct aspect ration.
    # # `tile=${COLS}x${ROWS}` Layout captured frames into this grid

    # print enire command for debugging purposes
    echo $FFMPEG_CMD

    eval $FFMPEG_CMD