Ticket #11170: trac_11170-animate.patch

File trac_11170-animate.patch, 24.2 KB (added by jhpalmieri, 9 years ago)
  • doc/en/installation/binary.rst

    # HG changeset patch
    # User J. H. Palmieri <palmieri@math.washington.edu>
    # Date 1302477116 25200
    # Node ID 08e3251639ad6edf04794446d515992fbc694e62
    # Parent  f8bdf65336cfdc59771dca5141e5b96d72c1d57b
    #11170: add ffmpeg option to animate
    
    diff --git a/doc/en/installation/binary.rst b/doc/en/installation/binary.rst
    a b disk space and the operating system is L 
    1717OS X (10.5.x).
    1818
    1919Highly Recommended: It is highly recommended that you have LaTeX
    20 installed.
     20installed.  If you want to view animations, you should install either
     21ImageMagick or ffmpeg.  ImageMagick or dvipng is also used for
     22displaying some LaTeX output in the Sage notebook.
    2123
    2224Download the latest binary tarball from
    2325http://www.sagemath.org/download.html . For example, it might be
  • doc/en/installation/source.rst

    diff --git a/doc/en/installation/source.rst b/doc/en/installation/source.rst
    a b a Fortran compiler on OS X): 
    5656       tar        (For Solaris or OpenSolaris, GNU tar, version 1.17 or later)
    5757       ssh-keygen (Needed to run the notebook in secure mode)
    5858       latex      (Highly recommended, though not strictly required)
     59       ImageMagick -- recommended
     60       ffmpeg -- recommended
     61       dvipng -- recommended
    5962
    6063The programs ``gcc``, ``g++`` and ``gfortran`` are all part of the `GNU Compiler Collection (GCC) <http://gcc.gnu.org/>`_.
    6164You are generally advised to use a recent version of GCC, though
    encrypted `HTTPS <http://en.wikipedia.or 
    8083``notebook(secure=True)`` instead of ``notebook()``. Unless ``notebook(secure=True)``
    8184is used, the notebook uses the less secure `HTTP <http://en.wikipedia.org/wiki/HTTP>`_ protocol
    8285
     86If you don't have either ImageMagick or ffmpeg, you won't be able to
     87view animations.  ffmpeg can produce animations in more different
     88formats than ImageMagick, and seems to be faster than ImageMagick when
     89creating animated GIFs.  Either ImageMagick or dvipng is used for
     90displaying some LaTeX output in the Sage notebook.
     91
    8392In OS X, make sure you have a recent version of `Xcode <http://developer.apple.com/xcode/>`_.
    8493See http://wiki.sagemath.org/SupportedPlatforms to find out what
    8594version(s) of Xcode are supported. You can get the latest Xcode
  • sage/plot/animate.py

    diff --git a/sage/plot/animate.py b/sage/plot/animate.py
    a b We plot a circle shooting up to the righ 
    66
    77    sage: a = animate([circle((i,i), 1-1/(i+1), hue=i/10) for i in srange(0,2,0.2)],
    88    ...               xmin=0,ymin=0,xmax=2,ymax=2,figsize=[2,2])
    9     sage: a.show() # optional -- requires convert command
     9    sage: a.show() # optional -- ImageMagick
    1010"""
    1111
    1212############################################################################
    class Animation(SageObject): 
    5151        Animation with 21 frames
    5252        sage: a[:5]
    5353        Animation with 5 frames
    54         sage: a.show()          # optional -- requires convert command
    55         sage: a[:5].show()      # optional
     54        sage: a.show()          # optional -- ImageMagick
     55        sage: a[:5].show()      # optional -- ImageMagick
    5656   
    5757    The ``show`` function takes arguments to specify the
    5858    delay between frames (measured in hundredths of a second, default
    class Animation(SageObject): 
    6060    means to iterate forever). To iterate 4 times with half a second
    6161    between each frame::
    6262   
    63         sage: a.show(delay=50, iterations=4) # optional
     63        sage: a.show(delay=50, iterations=4) # optional -- ImageMagick
    6464   
    6565    An animation of drawing a parabola::
    6666   
    class Animation(SageObject): 
    7171        ...       L += line([(i,i^2),(i+step,(i+step)^2)], rgbcolor=(1,0,0), thickness=2)
    7272        ...       v.append(L)
    7373        sage: a = animate(v, xmin=0, ymin=0)
    74         sage: a.show() # optional -- requires convert command
     74        sage: a.show() # optional -- ImageMagick
    7575        sage: show(L)
    7676   
    7777    TESTS: This illustrates that ticket #2066 is fixed (setting axes
    class Animation(SageObject): 
    8484   
    8585        sage: a = animate([plot(sin(x + float(k)), (0, 2*pi), ymin=-5, ymax=5)
    8686        ...            for k in srange(0,2*pi,0.3)])
    87         sage: a.show() # optional
     87        sage: a.show() # optional -- ImageMagick
    8888    """
    8989    def __init__(self, v, **kwds):
    9090        r"""
    class Animation(SageObject): 
    144144        EXAMPLES::
    145145       
    146146            sage: a = animate([x, x^2, x^3, x^4])
    147             sage: a[2].show()       # optional -- requires convert command
     147            sage: a[2].show()       # optional -- ImageMagick
    148148        """
    149149        return self.__frames[i]
    150150
    class Animation(SageObject): 
    158158            ...               xmin=0,ymin=-2,xmax=2,ymax=0,figsize=[2,2])
    159159            sage: a
    160160            Animation with 10 frames
    161             sage: a.show() # optional -- requires convert command
     161            sage: a.show() # optional -- ImageMagick
    162162            sage: a[3:7]
    163163            Animation with 4 frames
    164             sage: a[3:7].show() # optional
     164            sage: a[3:7].show() # optional -- ImageMagick
    165165        """
    166166        return Animation(self.__frames.__getslice__(*args), **self.__kwds)
    167167
    class Animation(SageObject): 
    191191       
    192192            sage: a = animate([circle((i,0),1) for i in srange(0,2,0.4)],
    193193            ...                xmin=0, ymin=-1, xmax=3, ymax=1, figsize=[2,1])
    194             sage: a.show()   # optional -- requires convert command
     194            sage: a.show()   # optional -- ImageMagick
    195195            sage: b = animate([circle((0,i),1,hue=0) for i in srange(0,2,0.4)],
    196196            ...                xmin=0, ymin=-1, xmax=1, ymax=3, figsize=[1,2])
    197197            sage: b.show() # optional
    198             sage: (a*b).show()    # optional
    199             sage: (a+b).show()    # optional
     198            sage: (a*b).show()    # optional -- ImageMagick
     199            sage: (a+b).show()    # optional -- ImageMagick
    200200        """
    201201        if not isinstance(other, Animation):
    202202            other = Animation(other)
    class Animation(SageObject): 
    221221       
    222222            sage: a = animate([circle((i,0),1,thickness=20*i) for i in srange(0,2,0.4)],
    223223            ...                xmin=0, ymin=-1, xmax=3, ymax=1, figsize=[2,1], axes=False)
    224             sage: a.show()     # optional -- requires convert command
     224            sage: a.show()     # optional -- ImageMagick
    225225            sage: b = animate([circle((0,i),1,hue=0,thickness=20*i) for i in srange(0,2,0.4)],
    226226            ...                xmin=0, ymin=-1, xmax=1, ymax=3, figsize=[1,2], axes=False)
    227             sage: b.show()             # optional
    228             sage: (a*b).show()         # optional
    229             sage: (a+b).show()         # optional
     227            sage: b.show()             # optional -- ImageMagick
     228            sage: (a*b).show()         # optional -- ImageMagick
     229            sage: (a+b).show()         # optional -- ImageMagick
    230230        """
    231231        if not isinstance(other, Animation):
    232232            other = Animation(other)
    class Animation(SageObject): 
    271271            sage: a = animate(v, xmin=0, ymin=0)
    272272            sage: a
    273273            Animation with 4 frames
    274             sage: a.show()        # optional -- requires convert command
     274            sage: a.show()        # optional -- ImageMagick
    275275       
    276276        ::
    277277       
    class Animation(SageObject): 
    296296        Returns an animated gif composed from rendering the graphics
    297297        objects in self.
    298298       
    299         This function will only work if the ImageMagick command line tools
    300         package is installed, i.e., you have the "``convert``"
    301         command.
    302        
     299        This function will only work if either (a) the ImageMagick
     300        software suite is installed, i.e., you have the ``convert``
     301        command or (b) ffmpeg is installed.  See www.imagemagick.org
     302        for more about ImageMagic, and see www.ffmpeg.org for more
     303        about ffmpeg.
     304
    303305        INPUT:
    304        
    305        
     306
    306307        -  ``delay`` - (default: 20) delay in hundredths of a
    307308           second between frames
    308        
     309
    309310        -  ``savefile`` - file that the animated gif gets saved
    310311           to
    311        
     312
    312313        -  ``iterations`` - integer (default: 0); number of
    313314           iterations of animation. If 0, loop forever.
    314        
     315
    315316        -  ``show_path`` - boolean (default: False); if True,
    316317           print the path to the saved file
    317        
    318        
     318
    319319        If savefile is not specified: in notebook mode, display the
    320320        animation; otherwise, save it to a default file name.
    321        
     321
    322322        EXAMPLES::
    323        
     323
    324324            sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.7)],
    325325            ...                xmin=0, xmax=2*pi, figsize=[2,1])
    326             sage: a.gif()              # optional -- requires convert command
    327             sage: a.gif(delay=35, iterations=3)       # optional
    328             sage: a.gif(savefile='my_animation.gif')  # optional
    329             sage: a.gif(savefile='my_animation.gif', show_path=True) # optional
     326            sage: dir = tmp_dir() + '/'
     327            sage: a.gif()              # not tested
     328            sage: a.gif(savefile=dir + 'my_animation.gif', delay=35, iterations=3)  # optional -- ImageMagick
     329            sage: a.gif(savefile=dir + 'my_animation.gif', show_path=True) # optional -- ImageMagick
    330330            Animation saved to .../my_animation.gif.
    331331       
    332332        .. note::
    333333
    334            If ImageMagick is not installed, you will get an error
    335            message like this::
    336        
     334           If neither ffmpeg nor ImageMagick is installed, you will
     335           get an error message like this::
     336
    337337              /usr/local/share/sage/local/bin/sage-native-execute: 8: convert:
    338338              not found
    339        
    340               Error: ImageMagick does not appear to be installed. Saving an
    341               animation to a GIF file or displaying an animation requires
    342               ImageMagick, so please install it and try again.
    343        
    344            See www.imagemagick.org, for example.
    345        
     339
     340              Error: Neither ImageMagick nor ffmpeg appears to be installed. Saving an
     341              animation to a GIF file or displaying an animation requires one of these
     342              packages, so please install one of them and try again.
     343
     344              See www.imagemagick.org and www.ffmpeg.org for more information.
     345
    346346        AUTHORS:
    347347
    348348        - William Stein
    349349        """
    350         if not savefile:
    351             savefile = sage.misc.misc.graphics_filename(ext='gif')
    352         if not savefile.endswith('.gif'):
    353             savefile += '.gif'
    354         savefile = os.path.abspath(savefile)
    355         d = self.png()
    356         cmd = 'cd "%s"; sage-native-execute convert -delay %s -loop %s *.png "%s"'%(d, int(delay), int(iterations), savefile)
    357         from subprocess import check_call, CalledProcessError
    358         try:
    359             check_call(cmd, shell=True)
    360             if show_path:
    361                 print "Animation saved to file %s." % savefile
    362         except (CalledProcessError, OSError):
    363             print ""
    364             print "Error: ImageMagick does not appear to be installed. Saving an"
    365             print "animation to a GIF file or displaying an animation requires"
    366             print "ImageMagick, so please install it and try again."
    367             print ""
    368             print "See www.imagemagick.org, for example."
     350        if self._have_ffmpeg():
     351            self.ffmpeg(savefile=savefile, show_path=show_path,
     352                        output_format='gif', delay=delay,
     353                        iterations=iterations)
     354        else:
     355            if not savefile:
     356                savefile = sage.misc.misc.graphics_filename(ext='gif')
     357            if not savefile.endswith('.gif'):
     358                savefile += '.gif'
     359            savefile = os.path.abspath(savefile)
     360            d = self.png()
     361            cmd = 'cd "%s"; sage-native-execute convert -delay %s -loop %s *.png "%s"'%(d, int(delay), int(iterations), savefile)
     362            from subprocess import check_call, CalledProcessError
     363            try:
     364                check_call(cmd, shell=True)
     365                if show_path:
     366                    print "Animation saved to file %s." % savefile
     367            except (CalledProcessError, OSError):
     368                msg = """
     369Error: Neither ImageMagick nor ffmpeg appears to be installed. Saving an
     370animation to a GIF file or displaying an animation requires one of these
     371packages, so please install one of them and try again.
     372
     373See www.imagemagick.org and www.ffmpeg.org for more information."""
     374                raise OSError, msg
    369375
    370376    def show(self, delay=20, iterations=0):
    371377        r"""
    class Animation(SageObject): 
    384390        .. note::
    385391
    386392           Currently this is done using an animated gif, though this
    387            could change in the future. This requires that the ImageMagick
    388            command line tools package be installed, i.e., that you have the
    389            ``convert`` command.
    390        
     393           could change in the future. This requires that either
     394           ffmpeg or the ImageMagick suite (in particular, the
     395           ``convert`` command) is installed.
     396
     397        See also the :meth:`ffmpeg` method.
     398
    391399        EXAMPLES::
    392        
     400
    393401            sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.7)],
    394402            ...                xmin=0, xmax=2*pi, figsize=[2,1])
    395             sage: a.show()       # optional -- requires convert command
     403            sage: a.show()       # optional -- ImageMagick
    396404       
    397405        The preceding will loop the animation forever. If you want to show
    398406        only three iterations instead::
    399        
    400             sage: a.show(iterations=3)    # optional
    401        
     407
     408            sage: a.show(iterations=3)    # optional -- ImageMagick
     409
    402410        To put a half-second delay between frames::
    403        
    404             sage: a.show(delay=50)        # optional
    405        
     411
     412            sage: a.show(delay=50)        # optional -- ImageMagick
     413
    406414        .. note::
    407415
    408            If ImageMagick is not installed, you will get an error
    409            message like this::
     416           If you don't have ffmpeg or ImageMagick installed, you will
     417           get an error message like this::
    410418       
    411419              /usr/local/share/sage/local/bin/sage-native-execute: 8: convert:
    412420              not found
    413        
    414               Error: ImageMagick does not appear to be installed. Saving an
    415               animation to a GIF file or displaying an animation requires
    416               ImageMagick, so please install it and try again.
    417        
    418             See www.imagemagick.org, for example.
     421
     422              Error: Neither ImageMagick nor ffmpeg appears to be installed. Saving an
     423              animation to a GIF file or displaying an animation requires one of these
     424              packages, so please install one of them and try again.
     425
     426              See www.imagemagick.org and www.ffmpeg.org for more information.
    419427        """
    420428        if plot.DOCTEST_MODE:
    421             self.gif(delay = delay, iterations = iterations)
     429            filename = sage.misc.misc.tmp_filename() + '.gif'
     430            self.gif(savefile=filename, delay=delay, iterations=iterations)
    422431            return
    423432       
    424433        if plot.EMBEDDED_MODE:
    class Animation(SageObject): 
    429438            os.system('%s %s 2>/dev/null 1>/dev/null &'%(
    430439                sage.misc.viewer.browser(), filename))
    431440
     441    def _have_ffmpeg(self):
     442        """
     443        Return True if the program 'ffmpeg' is installed.  See
     444        www.ffmpeg.org to download ffmpeg.
     445
     446        EXAMPLES::
     447
     448            sage: a = animate([plot(sin, -1,1)], xmin=0, ymin=0)
     449            sage: a._have_ffmpeg() # random: depends on whether ffmpeg is installed
     450            False
     451        """
     452        from sage.misc.sage_ostools import have_program
     453        return have_program('ffmpeg')
     454
     455    def ffmpeg(self, savefile=None, show_path=False, output_format=None,
     456               ffmpeg_options='', delay=None, iterations=0, verbose=False):
     457        """
     458        Returns a movie showing an animation composed from rendering
     459        the graphics objects in self.
     460
     461        This function will only work if ffmpeg is installed.  See
     462        http://www.ffmpeg.org for information about ffmpeg.
     463
     464        INPUT:
     465
     466        -  ``savefile`` - file that the mpeg gets saved to.
     467
     468        .. warning:
     469
     470            This will overwrite ``savefile`` if it already exists.
     471
     472        - ``show_path`` - boolean (default: False); if True, print the
     473          path to the saved file
     474
     475        - ``output_format`` - string (default: None); format and
     476          suffix to use for the video.  This may be 'mpg', 'mpeg',
     477          'avi', 'gif', or any other format that ffmpeg can handle.
     478          If this is None and the user specifies ``savefile`` with a
     479          suffix, say 'savefile=animation.avi', try to determine the
     480          format ('avi' in this case) from that file name.  If no file
     481          is specified or if the suffix cannot be determined, 'mpg' is
     482          used.
     483
     484        - ``ffmpeg_options`` - string (default: ''); this string is
     485          passed directly to ffmpeg.
     486
     487        - ``delay`` - integer (default: None) delay in hundredths of a
     488          second between frames; i.e., the framerate is 100/delay.
     489          This is not supported for mpeg files: for mpegs, the frame
     490          rate is always 25 fps.
     491
     492        - ``iterations`` - integer (default: 0); number of iterations
     493          of animation. If 0, loop forever.  This is only supported
     494          for animated gif output.
     495
     496        - ``verbose`` - boolean (default: False); if True, print
     497          messages produced by the ffmpeg command.
     498
     499        If savefile is not specified: in notebook mode, display the
     500        animation; otherwise, save it to a default file name.
     501
     502        EXAMPLES::
     503
     504            sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.7)],
     505            ...                xmin=0, xmax=2*pi, figsize=[2,1])
     506            sage: dir = tmp_dir() + '/'
     507            sage: a.ffmpeg(savefile=dir + 'new.mpg')       # optional -- ffmpeg
     508            sage: a.ffmpeg(savefile=dir + 'new.avi')       # optional -- ffmpeg
     509            sage: a.ffmpeg(savefile=dir + 'new.gif')       # optional -- ffmpeg
     510            sage: a.ffmpeg(savefile=dir + 'new.mpg', show_path=True) # optional -- ffmpeg
     511            Animation saved to .../new.mpg.
     512
     513        .. note::
     514
     515           If ffmpeg is not installed, you will get an error message
     516           like this::
     517
     518              Error: ffmpeg does not appear to be installed. Saving an animation to
     519              a movie file in any format other than GIF requires this software, so
     520              please install it and try again.
     521
     522           See www.ffmpeg.org for more information.
     523        """
     524        if not self._have_ffmpeg():
     525            msg = """Error: ffmpeg does not appear to be installed. Saving an animation to
     526a movie file in any format other than GIF requires this software, so
     527please install it and try again."""
     528            raise OSError, msg
     529        else:
     530            if not savefile:
     531                if output_format is None:
     532                    output_format = 'mpg'
     533                savefile = sage.misc.misc.graphics_filename(ext=output_format)
     534            else:
     535                if output_format is None:
     536                    suffix = os.path.splitext(savefile)[1]
     537                    if len(suffix) > 0:
     538                        suffix = suffix.lstrip('.')
     539                        output_format = suffix
     540                    else:
     541                        output_format = 'mpg'
     542            if not savefile.endswith('.' + output_format):
     543                savefile += '.' + output_format
     544            early_options = ''
     545            if output_format == 'gif':
     546                ffmpeg_options += ' -pix_fmt rgb24 -loop_output %s ' % iterations
     547            if delay is not None and output_format != 'mpeg' and output_format != 'mpg':
     548                early_options += ' -r %s -g 3 ' % int(100/delay)
     549            savefile = os.path.abspath(savefile)
     550            pngdir = self.png()
     551            pngs = os.path.join(pngdir, "%08d.png")
     552            # For ffmpeg, it seems that some options, like '-g ... -r
     553            # ...', need to come before the input file names, while
     554            # some options, like '-pix_fmt rgb24', need to come
     555            # afterwards.  Hence 'early_options' and 'ffmpeg_options'
     556            cmd = 'cd "%s"; sage-native-execute ffmpeg -y -f image2 %s -i %s %s %s' % (pngdir, early_options, pngs, ffmpeg_options, savefile)
     557            from subprocess import check_call, CalledProcessError, PIPE
     558            try:
     559                if verbose:
     560                    print "Executing %s " % cmd
     561                    check_call(cmd, shell=True)
     562                else:
     563                    check_call(cmd, shell=True, stderr=PIPE)
     564                if show_path:
     565                    print "Animation saved to file %s." % savefile
     566            except (CalledProcessError, OSError):
     567                print "Error running ffmpeg."
     568                raise
     569
    432570    def save(self, filename=None, show_path=False):
    433571        """
    434         Save this animation into a gif or sobj file.
    435        
     572        Save this animation.
     573
    436574        INPUT:
    437        
    438        
     575
    439576        -  ``filename`` - (default: None) name of save file
    440        
     577
    441578        -  ``show_path`` - boolean (default: False); if True,
    442579           print the path to the saved file
    443        
    444        
    445         If filename is None, then in notebook mode, display the animation;
    446         otherwise, save the animation to a gif file. If filename ends in
    447         '.gif', save to a gif file. If filename ends in '.sobj', save to an
    448         sobj file.
    449        
    450         In all other cases, print an error.
    451        
     580
     581        If filename is None, then in notebook mode, display the
     582        animation; otherwise, save the animation to a GIF file.  If
     583        filename ends in '.sobj', save to an sobj file.  Otherwise,
     584        try to determine the format from the filename extension
     585        ('.mpg', '.gif', '.avi', etc.).  If the format cannot be
     586        determined, default to gif.
     587
     588        For GIF files, either ffmpeg or the ImageMagick suite must be
     589        installed.  For other movie formats, ffmpeg must be installed.
     590        An sobj file can be saved with no extra software installed.
     591
    452592        EXAMPLES::
    453        
     593
    454594            sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.7)],
    455595            ...                xmin=0, xmax=2*pi, figsize=[2,1])
    456             sage: a.save()         # optional -- requires convert command
    457             sage: a.save('wave.gif')   # optional
    458             sage: a.save('wave.gif', show_path=True)   # optional
     596            sage: dir = tmp_dir() + '/'
     597            sage: a.save()         # not tested
     598            sage: a.save(dir + 'wave.gif')   # optional -- ImageMagick
     599            sage: a.save(dir + 'wave.gif', show_path=True)   # optional -- ImageMagick
    459600            Animation saved to file .../wave.gif.
    460             sage: a.save('wave0.sobj')  # optional
    461             sage: a.save('wave1.sobj', show_path=True)  # optional
    462             Animation saved to file wave1.sobj.
     601            sage: a.save(dir + 'wave.avi', show_path=True)   # optional -- ffmpeg
     602            Animation saved to file .../wave.avi.
     603            sage: a.save(dir + 'wave0.sobj')
     604            sage: a.save(dir + 'wave1.sobj', show_path=True)
     605            Animation saved to file .../wave1.sobj.
    463606        """
    464         if filename is None or filename.endswith('.gif'):
     607        if filename is None:
     608            suffix = '.gif'
     609        else:
     610            suffix = os.path.splitext(filename)[1]
     611            if len(suffix) == 0:
     612                suffix = '.gif'
     613
     614        if filename is None or suffix == '.gif':
    465615            self.gif(savefile=filename, show_path=show_path)
    466616            return
    467         elif filename.endswith('.sobj'):
     617        elif suffix == '.sobj':
    468618            SageObject.save(self, filename)
    469619            if show_path:
    470620                print "Animation saved to file %s." % filename
    471621            return
    472622        else:
    473             raise ValueError, "Unable to save to a file with the extension '%s'"%(
    474                 os.path.splitext(filename)[1][1:])
     623            self.ffmpeg(savefile=filename, show_path=show_path)
     624            return