An "automatic" screenshot taker is something that I've always wanted, but the commercial offerings leave much to be desired and the only other option seems to be the "manual" approach. I am of course talking about screenshots from video files rather than screenshots of your desktop, that sort of thing is well covered.
One of the problems with making your own is that the options are fairly limited on just how you go about opening video files and pulling out the candy frame goodness. For Windows users, the option is to use DirectShow which I can only describe as The Crystal Maze for it's Byzantine ways of operating are beyond mortal ken. The other option is to use a pre-built library such as ffmpeg or similar. This was out as well as not only was it a whole new way of working for me (Windows development files were few and far between) it was a whole new set of a programming challenges which made the learning curve more of a learning cliff.
"during testing I had a number of problems with this"
So I turned forlornly to existing media-players in the slim hope that one of them would have the abilities required for scripting a makeshift screenshotter. Media Player Classic has limited command line support, VLC is more geared towards client/server setup and I couldn't even figure out whether that route would lead to any semblance of success, BSPlayer... The list goes on as to the number of players which don't supply a full body of command line options.
The silver lining, the angel of hope was MPlayer. If you're prepared to wade through a bit of fudge to get there, MPlayer provides everything you need to script a screenshotter:
- jump to any part of the file from the command line
- output into different (static) formats such as PNG and JPEG
- can output file information (length, dimensions etc.)
With these three functions MPlayer is almost all you need. Almost.
First of all you need to get the MPlayer release for your architecture, for the majority of screenshot monkies, that's Windows. Puncturing the MPlayer-Windows mantle takes a bit of pushing but essentially you can usually get away with just downloading the latest build. This gives you support for a whole heap of formats (XviD, DivX, x264 and so on), however some encoders prefer to eschew open-source and go with Windows Media Video (usually of the "9" flavour). This is not available by default (as MPlayer uses ffmpeg/libavcodec and not DirectShow) so you need to grab an ethereally named codec package and dump them into your MPlayer install directory. With a bit of luck and perhaps a bit of document searching you'll have yourself a fully working command-line media player.
Now for the easy bit, the scripting. I chose to use PHP simply because I use it on a day-to-day basis, any kind of scripting would work though. With some document digging you can find the list of command line options. For our purposes we're only going to need to run MPlayer in two "modes": the first is pulling the pertinent information from the video file we're going to grab screenshots from, the second is actually pulling the screenshots out of the file.
Identification
-vo null -nosound -frames 0 -identify
For the impatient this sets the video output to null, disables sound, doesn't output any frames and prints out identifying features of the video file.
Screenshotting
-really-quiet -vo jpeg:progressive:quality=90 -nosound -ss {seek} -frames 2
This turns off most informational otuput, sets the video output to JPEG with nice options, disables sound, seeks to the specific point in the video then outputs 2 frames (the reason for which I will explain shortly).
First task is to get information about the file and find out the total length of the video/stream. Running the identification command-line arguments with MPlayer and capturing the output is a simple case of running shell_exec or using backticks, whichever you prefer.
$infoOutput = shell_e xec("{$mplayerCommand} {$argsInfo} \"{$_SERVER['argv'][1]}\"");
//$matcher = '/ID_VIDEO_WIDTH=(\d+)$|ID_VIDEO_HEIGHT=(\d+)$|ID_LENGTH=(\d+\.\d+)$/m';
$matcher = '/ID_LENGTH=(\d+\.\d+)$/m';
$matches = array();
preg_match_all($matcher, $infoOutput, $matches);
$fileLength = (!empty($matches[1][0])) ? floatval($matches[1][0]) : 1140;
The filename is pulled from the "argv" array passed to command line scripts. The commented out regex is for if you wanted to get the pixel dimensions of the video file, as it is we don't need this information so the regex that is used is simpler and more compact. The file-length is output in seconds.milliseconds format which is then cast (as much as anything can be cast in PHP) as a float or assumed to be 24 minutes if nothing was matched.
The next step is to work out the interval at which you're going to take screenshots. This is usually defined either by a frequency (take a screenshot every X seconds) or by the number of screenshots to take (100).
Now there is an MPlayer command line option to skip a certain number of seconds after each frame (-sstep <sec>) however during testing I had a number of problems with this which is why I use this less elegant but more foolproof method:
for($i = 0; $i < $screenshotCount; $i++)
{
$offset = $i * $increment;
$tempArgs = str_replace('{seek}', $offset, $argsPlay);
shell_e xec("{$mplayerCommand} {$tempArgs} \"{$_SERVER['argv'][1]}\"");
}
From the example arguments provided above, this replaces the "{seek}" token with our second offset (worked out previously) and then executes the MPlayer command with our screenshot arguments. This will dump 2 files into our working directory (use chdir to set your working directory): "00000001.jpg" and "00000002.jpg".
Now, the reason for using 2 frames instead of just one is that for certain video types (WMV mostly), the first shot that is taken is always blank. The dimensions are correct, but the screenshot is just black. This is fixed by taking two screenshots as the second shot always has the content there. This is a bit of a fudge but it gets around this bizarre little occurrence.
That's the meat of the screenshotter, there's a lot which has been omitted such as ensuring the passed file exists, renaming the output files (this is neccessary otherwise the files are overwritten each cycle) and unlinking the first (possibly blank) screenshot. From here you can probably work out things yourself however I have constructed a relatively petite script which I'm going to release under the Creative Commons Attribution License.
The main variables you'll want to edit are $mplayerCommand, $screenshotDir and $ssCount or $ssFreq. The script creates a subdirectory within $screenshotDir of the name of the file then pumps all of the screenshots into that directory, numbering them sequentially. I have a shortcut to the script on my desktop which I can then just drag video files onto (which does the neat thing of simply appending the absolute filename onto the end of the command line which simplifies usage immensely).
While you can set a frequency to take screenshots at, I would strongly recommend using an absolute count as a 24 minute video file with 1 screenshot a second gives you 1440 screenshots which can take quite a while to finish and, depending on your video, not all of the shots will necessarily be very good.
Ways forward for this include perhaps doing batch image adjustment (levels, sharpening) as well as automatic thumbnailing (something my gallery already does and hence omitted from this script). The built-in GD would be more than adequete for something like this although ImageMagick is perhaps swifter and more powerful but would add further overhead to the otherwise neat package.
Fundamentally I've found that taking a number of screenshots then cherry-picking the best is really the only way this script is useful. It's good for giving on overall view of a video file rather than specific scenes within a file, for that, the "take screenshot" shortcut key is still king.
Addendum: The eagle-eyed amongst you will notice that the "shell_exec" command in the code above has a space between the "e" and "x" in "exec", as far as I can tell the plugin I'm using to keep the code formatting breaks WordPress when I leave the command in full in-between <code> tags. Bad Preserve code formatting.