Skip to main content

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]


slow down


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

Popular posts from this blog

plotting - Filling between two spheres in SphericalPlot3D

Manipulate[ SphericalPlot3D[{1, 2 - n}, {θ, 0, Pi}, {ϕ, 0, 1.5 Pi}, Mesh -> None, PlotPoints -> 15, PlotRange -> {-2.2, 2.2}], {n, 0, 1}] I cant' seem to be able to make a filling between two spheres. I've already tried the obvious Filling -> {1 -> {2}} but Mathematica doesn't seem to like that option. Is there any easy way around this or ... Answer There is no built-in filling in SphericalPlot3D . One option is to use ParametricPlot3D to draw the surfaces between the two shells: Manipulate[ Show[SphericalPlot3D[{1, 2 - n}, {θ, 0, Pi}, {ϕ, 0, 1.5 Pi}, PlotPoints -> 15, PlotRange -> {-2.2, 2.2}], ParametricPlot3D[{ r {Sin[t] Cos[1.5 Pi], Sin[t] Sin[1.5 Pi], Cos[t]}, r {Sin[t] Cos[0 Pi], Sin[t] Sin[0 Pi], Cos[t]}}, {r, 1, 2 - n}, {t, 0, Pi}, PlotStyle -> Yellow, Mesh -> {2, 15}]], {n, 0, 1}]

plotting - Plot 4D data with color as 4th dimension

I have a list of 4D data (x position, y position, amplitude, wavelength). I want to plot x, y, and amplitude on a 3D plot and have the color of the points correspond to the wavelength. I have seen many examples using functions to define color but my wavelength cannot be expressed by an analytic function. Is there a simple way to do this? Answer Here a another possible way to visualize 4D data: data = Flatten[Table[{x, y, x^2 + y^2, Sin[x - y]}, {x, -Pi, Pi,Pi/10}, {y,-Pi,Pi, Pi/10}], 1]; You can use the function Point along with VertexColors . Now the points are places using the first three elements and the color is determined by the fourth. In this case I used Hue, but you can use whatever you prefer. Graphics3D[ Point[data[[All, 1 ;; 3]], VertexColors -> Hue /@ data[[All, 4]]], Axes -> True, BoxRatios -> {1, 1, 1/GoldenRatio}]

plotting - Adding a thick curve to a regionplot

Suppose we have the following simple RegionPlot: f[x_] := 1 - x^2 g[x_] := 1 - 0.5 x^2 RegionPlot[{y < f[x], f[x] < y < g[x], y > g[x]}, {x, 0, 2}, {y, 0, 2}] Now I'm trying to change the curve defined by $y=g[x]$ into a thick black curve, while leaving all other boundaries in the plot unchanged. I've tried adding the region $y=g[x]$ and playing with the plotstyle, which didn't work, and I've tried BoundaryStyle, which changed all the boundaries in the plot. Now I'm kinda out of ideas... Any help would be appreciated! Answer With f[x_] := 1 - x^2 g[x_] := 1 - 0.5 x^2 You can use Epilog to add the thick line: RegionPlot[{y < f[x], f[x] < y < g[x], y > g[x]}, {x, 0, 2}, {y, 0, 2}, PlotPoints -> 50, Epilog -> (Plot[g[x], {x, 0, 2}, PlotStyle -> {Black, Thick}][[1]]), PlotStyle -> {Directive[Yellow, Opacity[0.4]], Directive[Pink, Opacity[0.4]],