-
-
Save OndraZizka/bd4a83413e49d1859e8dbfe3167d4c9b to your computer and use it in GitHub Desktop.
Source code for Binpress tutorial: http://www.binpress.com/tutorial/generating-nice-movie-previews-with-ffmpeg/138
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
Tutorial code for: http://www.binpress.com/tutorial/generating-nice-movie-previews-with-ffmpeg/138 | |
--> | |
<!DOCTYPE html> | |
<html lang="en"> | |
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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: http://www.binpress.com/tutorial/generating-nice-movie-previews-with-ffmpeg/138 | |
# OndraZizka: | |
# * Fixed handling of path with spaces | |
# * The preview image is now generated next to the movie (use OUT=here $0 ... to have the original behavior) | |
# * Default tiles is now 8x4 | |
if [ -z "$1" ]; then | |
#echo "usage: ./movie_preview.sh VIDEO [HEIGHT=120] [COLS=100] [ROWS=1] [OUTPUT]" | |
echo "usage: ./movie_preview.sh VIDEO [HEIGHT=240] [COLS=8] [ROWS=4] [OUTPUT]" | |
exit | |
fi | |
MOVIE="$1" | |
# get video name without the path and extension | |
MOVIE_NAME="`basename "$MOVIE"`" | |
OUT_DIR="`pwd`" | |
if [ "$OUT" == "here" ] ; then | |
OUT_DIR="`pwd`" | |
else | |
OUT_DIR="`dirname "$MOVIE"`" | |
fi | |
HEIGHT=$2 | |
COLS=$3 | |
ROWS=$4 | |
OUT_FILENAME="$5" | |
if [ -z "$HEIGHT" ]; then | |
HEIGHT=240 | |
fi | |
if [ -z "$COLS" ]; then | |
COLS=8 | |
fi | |
if [ -z "$ROWS" ]; then | |
ROWS=4 | |
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` | |
# 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 | |
echo "$OUT_FILEPATH" | |
eval $FFMPEG_CMD |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment