export - Does Mathematica support variable frame rate for any video format, in analogue of GIF-style "DisplayDurations"?
The good old GIF
animation format allows us to set the duration of each individual frame in the animation separately. This is especially useful if some frames in the animation sequence are to remain static for an extended period of time, because such delays don't increase the file size of the GIF
. Here is an example:
frames = Table[
Graphics[
Text[Style["Slow Down", FontFamily -> "Futura", FontColor -> Blue,
FontSize -> 48], {0, y}], Background -> Cyan,
PlotRange -> {{-1, 1}, {.1, 1.5}}], {y, 1.2, .2, -.1}];
Export["slowDownMovie.gif", frames,
"DisplayDurations" -> Append[Range[Length[frames] - 1]/20, 2],
"AnimationRepetitions" -> Infinity]
Even though the last frames are very long in duration, that has no effect on the file size.
Of course one thing GIF
doesn't have is a playback control, which allows you to pause and rewind a movie. To combine these two features, it would be nice if there were a movie export format in which one can set individual frame delays (variable frame rate), instead of a global frame rate. The problem with a global frame rate is that you have to duplicate frames if you want them to stay on the screen longer.
I know that the QuickTime
movie format officially supports embedding image sequences and giving each image a separate display duration. This is an old feature of the QuickTime
container structure that doesn't appear to be used much. Many other formats seem to support it too.
Another motivation: Many people nowadays make movies of slide presentations, which is often quite nice. But sometimes it's also wasteful, if you don't need audio and have just a sequence of stills. The above image-sequence approach (with playhead and rewind) would be a more efficient way of running such presentations with pre-set timing.
So my question is: are any of the available movie export formats in Mathematica able to accept an option analogous to the "DisplayDurations" option that GIF
has?
I know how to make such image sequences for QuickTime
on the Mac, but only with the help of external function calls using the QTKit
(built-in) library. I couldn't find any documented options for this in the export help. If it turns out that there is no platform-independent way of doing this from Mathematica, I'd also be interested in OS-dependent solutions, and I'll post my own approach later.
Answer
This is a solution specifically using the QTKit
library in Mac OS X. I'm calling it through the built-in Python interface, using a script that could also be run as a Python module or in standalone mode.
Since the goal here is to make a Mathematica function, I wrapped the call in a Run
command inside a function exportMov
. This required stuffing the Python code into a string that shows up as a single huge line in the code below (Python is sensitive to the placement of newlines and indentation, so the spaces and \n
characters in the string are all intentional). I've used the same technique for calling a Python script on the fly in this answer before.
Edit: The Python code is posted in a more readable and re-usable form on my website. End Edit.
exportMov[name_String, frames_List, delays_: {.03}] := Module[
{
outFile = ExpandFileName[name],
fileNames,
script ="printf \"from Foundation import NSNumber\nfrom AppKit import NSImage\nfrom QTKit import *\n\nclass QuickTimeError(Exception):\n @classmethod\n def from_nserror(cls, nserror):\n return cls(nserror.userInfo()['NSLocalizedDescription'])\n\ndef createQT(infiles, sequence, durations, outfile):\n attrs = {QTAddImageCodecType: 'avc1', QTAddImageCodecQuality: NSNumber.numberWithLong_(codecHighQuality)}\n mov, err = QTMovie.alloc().initToWritableFile_error_(outfile, None)\n if mov is None:\n raise QuickTimeError.from_nserror(err)\n n = len(durations)-1\n i = 0\n for index in sequence:\n img = NSImage.alloc().initWithContentsOfFile_(infiles[sequence[index]])\n t = durations[i]\n if i1:\n durations.append(float(row[1]))\n else:\n durations.append(30)\n else:\n print row[0]+': File not found'\n if len(imagefiles)==0:\n print 'No images found. Movie not created'\n else:\n print 'Creating movie from frames'\n outfileName = os.path.abspath('out.mov')\n createQT(imagefiles, range(len(durations)), durations, outfileName) \n\" | /usr/bin/python",
tempDir =
FileNameJoin[{$TemporaryDirectory,
"MathematicaOutput" <> StringJoin[Map[ToString, DateList[]]]}]
},
CreateDirectory[tempDir];
SetDirectory[tempDir];
Export["frame00000001.png", frames, "VideoFrames"];
fileNames = FileNames[];
Export["buildFile",
MapThread[(# <> " " <> ToString[#2]) &, {fileNames,
600 PadRight[delays, Length[fileNames], delays]}], "Text"];
(* Process files *)
Run[script];
CopyFile["out.mov", outFile];
(* Cleanup: *)
Map[DeleteFile, FileNames[]];
ResetDirectory[];
DeleteDirectory[tempDir]
]
The example movie for which I defined the frames in the question above can now be exported as follows:
exportMov["slowDownMovie.mov", frames,
Append[Range[Length[frames] - 1]/20, 2]]
The frames are first exported as single images into a temporary directory, then assembled into a movie with the name given as the first argument.
The third argument to exportMov
is the list of frame durations, and that is of course the whole point of this function. In the above example, I used the same list of durations as for the GIF
animation. The list of durations is optional (when omitted, a constant default duration is used). If the list of durations is shorter than the list of frames, then the given durations are repeated cyclically until all frames are used up.
Comments
Post a Comment