#!/usr/bin/perl # Movies In One - Steven Goodwin 2010 # Released under the GNU GPL, v2 # This is a quirky script which takes a movie file, and renders the whole # thing into a single animated gif where each frame comprises a montage of # 20 frames from the movie. my $imageDirectory = "/net/media/movies/s/asd/frames"; my $filename = "/net/media/movies/s/A.Scanner.Darkly.avi"; # Number of frames in each montage my $width = 5; my $height = 2; # Number of frames my $montageFrames = 20; my $animationSpeed = 40; my $sourceImageFrameCount; my $filePrefix = ""; my $firstFileIndex; # Find duration in frames my $cmd = `mplayer -vo null -ao null -frames 0 -identify "$filename" 2>/dev/null | sed -ne '/^ID_/ { s/[]()|&;<>\`'"'"'\\!$" []/\\&/g;p }'`; # ID_LENGTH=6022.56 is number of frames $cmd =~ /ID_LENGTH=(.*)\n/sg; my $duration = $1; # Rip frames my $imagesPerMontageFrame = $width * $height; my $totalFrames = $imagesPerMontageFrame * $montageFrames; my $frameGap = $duration / $totalFrames; my $frame = 0; my $currentTime = 0; if (0) { print "Capturing $totalFrames frames...\n"; while($frame < $totalFrames) { my $targetFile = "$filePrefix$frame.png"; if (! -e $targetFile) { `ffmpeg -i $filename -ss $currentTime -f image2 -vframes 1 -vcodec png grab%d.png >/dev/null 2>/dev/null`; `mv grab1.png "$targetFile"`; } $currentTime += $frameGap; ++$frame; if (($frame % 100) == 0) { print "Now at $frame...\n"; } } $sourceImageFrameCount = $totalFrames; $firstFileIndex = 0; } else { # When the images have already been ripped, we need to skip # over some, since there may be more frames than needed. # (which happens when every frame is ripped, for speed, but # not ultimately needed.) my $lastImage = `ls $imageDirectory | cut -d " " -f 9 | sort -n -r| head -n 1`; $lastImage =~ /^(.*)\./; $sourceImageFrameCount = $1; $firstFileIndex = 1; } # Determine montage size my $offsetFrame = 0; my $montageList = ""; my $rescaledImageIndex; for (my $currentMontageFrame=0;$currentMontageFrame<$montageFrames;++$currentMontageFrame) { $montageCommand = "montage -tile ${width}x$height "; # $montageCommand .= " -background #336699 -geometry +2+2 -border 5 "; # Image would run, in a 2 montage frame version, # M1: 0 2 4 6 M2: 1 3 5 7 # 8 10 12 14 9 11 13 15 # i.e. += montageFrames my $img=$offsetFrame; for(my $i=0;$i<$imagesPerMontageFrame;++$i) { # $img is currently the 'logical' image index. If the resultant # animated montage has 100 frames, $img ranges between 0 and 99 # If there's more frames, then we skip. I use a computed ratio # for each frame because - although slower - gives better # accuracy and better spread of used frames. $rescaledImageIndex = ($img * $sourceImageFrameCount) / $totalFrames; $rescaledImageIndex = int($rescaledImageIndex); $rescaledImageIndex += $firstFileIndex; $montageCommand .= " $imageDirectory/$filePrefix$rescaledImageIndex.png"; $img += $montageFrames; } print "Building montage $currentMontageFrame.png...\n"; my $montageFilename = " $imageDirectory/montage$currentMontageFrame.png"; $montageCommand .= $montageFilename; $montageList .= $montageFilename; `$montageCommand`; ++$offsetFrame; } # Combine all montages in a complete clip $filename=~/.*\/(.*?)$/; my $stubname = $1; print "Saving $stubname...\n"; `convert -delay $animationSpeed $montageList -loop 0 $stubname.gif`; # Simples!