# Save animation with Luxor.jl

**URL:** <https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861>\
**Category:** General Usage\
**Tags:** question, package, luxor, save, animations\
**Created:** [October 11, 2023, 3:12pm UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861 "2023-10-11T15:12:08Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![aleppersico](https://avatars.discourse-cdn.com/v4/letter/a/51bf81/32.png) [@aleppersico](https://discourse.julialang.org/u/aleppersico)\
**Post date:** [October 11, 2023, 3:12pm UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861/1 "2023-10-11T15:12:08Z")

</div>

Hello 😊,  
I have just learned how to produce drawings and animation with Luxor.jl ([Introduction to Luxor · Luxor](http://juliagraphics.github.io/Luxor.jl/stable/)) using Luxor.animate, Movie and frame function. However, I cannot find a way to save my animation in .mp4, .gif or other format. No info is present in the documentation.

Can anyone help me here?

Thanks!!

---

<div class="post-metadata">

**Author:** ![cormullion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cormullion/32/49131_2.png) [@cormullion](https://discourse.julialang.org/u/cormullion)\
**Post date:** [October 11, 2023, 3:36pm UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861/2 "2023-10-11T15:36:35Z")

</div>

The documentation contains quite a lot of information:

[http://juliagraphics.github.io/Luxor.jl/stable/howto/animation/](http://juliagraphics.github.io/Luxor.jl/stable/howto/animation/)

There’s

```julia
The creategif option for the animate function runs ffmpeg 
when the frames have all been generated.

```

and there’s

```julia
function main()
    databuffer = zeros(ARGB32, 250, 250)
    demo = Movie(250, 250, "buffer")
    animate(demo, [
            Scene(demo, (s, f) -> frame(s, f, databuffer),
                0:100)
        ],
        tempdirectory="/tmp/tempdir",
        creategif=true, 
        pathname="/tmp/t.gif")
end

```

where you can ask for a GIF and specify the pathname.

and there’s

```julia
...
tempdirectory = "/tmp/temp/"

animate(movie, [
        Scene(movie, frame, 1:50)
    ], 
    creategif=false, # don't have to create the GIF here
    tempdirectory=tempdirectory)

# run a custom ffmpeg command
FFMPEG.ffmpeg_exe(`-r 30 -f image2 -i $(tempdirectory)/%10d.png -c:v libx264 -r 30 -pix_fmt yuv420p -y /tmp/animation.mp4`)

```

where you can run FFMPEG to make an MP4.

Basically what you’re doing with `animate()` is creating a list of PNG files, with the option of running FFMPEG.

There are alternative ways of stitching PNG files together. For example, you can load them into a movie making application, or use something like Gifbrewery, which has a nifty Stitch feature.

---

<div class="post-metadata">

**Author:** ![aleppersico](https://avatars.discourse-cdn.com/v4/letter/a/51bf81/32.png) [@aleppersico](https://discourse.julialang.org/u/aleppersico)\
**Post date:** [October 12, 2023, 7:34am UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861/3 "2023-10-12T07:34:01Z")

</div>

Thanks for the reply!

I tried to use those commands but I am not able to make a .mp4 file and to run it. At best I can save all frames as separated .png files, or a single .gif file which is a collection of .png files (it does not visualise as a usual .gif file, it’s not animated).

Do you have any suggestion on how to proceed? 🙂

---

<div class="post-metadata">

**Author:** ![cormullion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cormullion/32/49131_2.png) [@cormullion](https://discourse.julialang.org/u/cormullion)\
**Post date:** [October 12, 2023, 8:16am UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861/4 "2023-10-12T08:16:13Z")

</div>

It’s hard to make a guess with so little information…

> [@Please read: make it easier to help you](https://discourse.julialang.org/t/please-read-make-it-easier-to-help-you/14757):
>
> Welcome to the Julia Discourse! We are enthusiastic about helping Julia programmers, both beginner and experienced. This public service announcement (PSA) outlines best practices when asking for help. Following these points makes it easier for us to help you and more likely you’ll get a prompt, useful answer. Keywords are highlighted to make it easier to refer to specific points. Choose a descriptive title that captures the key part of your question, eg “plots with multiple axes” instead of …

Once we know a bit more about your code and how you’re running it - and can see the messages that Julia outputs - it will be easier to make suggestions.

---

<div class="post-metadata">

**Author:** ![aleppersico](https://avatars.discourse-cdn.com/v4/letter/a/51bf81/32.png) [@aleppersico](https://discourse.julialang.org/u/aleppersico)\
**Post date:** [October 12, 2023, 8:58am UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861/5 "2023-10-12T08:58:44Z")

</div>

Ok I can post here a simplified version of my code:

```julia
using Printf
using Luxor

```

Function `draw` to create a simple drawing (rectangle with number inside; the number comes from an external `output`):

```julia
function draw(output,framenumber)
        
    # Settings
    x0 = 70; y0 = 100
    background("white")
    sethue("black")
    fontsize(20)
    
    # Reactangle
    rect(Point(x0-200,y0-200), 200, 200, action=:stroke)
    strokepath()
    # Output
    Luxor.text(string(@sprintf("%.0f",output[framenumber])),
         Point(x0-100,y0-100),
         halign=:center)
end

```

Function `frame` that calls `draw` to produce each single frame of the animation (`scene.opts` is needed to pass to `draw` the vector `output`):

```julia
function frame(scene, framenumber)
    # Draw the figure
    draw(scene.opts,framenumber)
end

```

Vector `output` and `movie` definitions:

```julia
output = 100:150
movie = Movie(800, 800, "name_movie", 1:10)

```

An animation is produced using `Luxor.animate` function (`optarg=output` is needed to pass the external `output`):

```julia
anim = Luxor.animate(movie,
[Scene(movie, frame, 1:10, easingfunction=easeinoutcubic, optarg=output)],
tempdirectory=pwd(),
creategif=false,
pathname=string(pwd(),"/video.gif"))

```

Now, with `tempdirectory=pwd()` I can save all ten frames as .png images in my current directory.  
With `creategif=true` I produce an animation visualised in Julia Plots space and saved in `pathname` with the specified name (`video.gif`). Indeed I see the animation in Julia Plots space and I save a file named `video.gif`, however that file is not an animation but only a collection of .png images. How to convert it into a real gif or a video (.mp4 or similar)? Even by changing `pathname` to something like `video.mp4` or others does not work.

I also tried to use `FFMPEG` package, but with no result at all (nothing is produced nor saved):

```julia
using FFMPEG
FFMPEG.ffmpeg_exe(`-r 30 -f image2 -i $(pwd())/%10d.png -c:v libx264 -r 30 -pix_fmt yuv420p -y /tmp/animation.mp4`)

```

Do you see any mistake or do you have any suggestion?

---

<div class="post-metadata">

**Author:** ![cormullion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cormullion/32/49131_2.png) [@cormullion](https://discourse.julialang.org/u/cormullion)\
**Post date:** [October 12, 2023, 9:14am UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861/6 "2023-10-12T09:14:43Z")

</div>

Your code works fine for me. With `creategif=true`, the GIF animation is saved in `video.gif`, and the MP4 is saved in `animation.mp4`.

 ![Screenshot 2023-10-12 at 10.10.59](https://global.discourse-cdn.com/julialang/original/3X/8/4/848865e4dac4b6dc773c0451a42a282754a370ad.png)

GIF:

![video](https://global.discourse-cdn.com/julialang/original/3X/a/1/a172b68e89ecc5cf1a1f958287ff1cf309d51757.gif)

MP4:

[https://global.discourse-cdn.com/julialang/original/3X/b/8/b87b085fe3d38eac5e5dadd701b921735321db51.mp4](https://global.discourse-cdn.com/julialang/original/3X/b/8/b87b085fe3d38eac5e5dadd701b921735321db51.mp4)

(Well, I’m not sure whether Discourse likes the MP4 format…)

---

<div class="post-metadata">

**Author:** ![aleppersico](https://avatars.discourse-cdn.com/v4/letter/a/51bf81/32.png) [@aleppersico](https://discourse.julialang.org/u/aleppersico)\
**Post date:** [October 12, 2023, 9:25am UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861/7 "2023-10-12T09:25:41Z")

</div>

Alright!! I just realised that using a Macbook I am not able to visualise the animated .gif with the default Preview app (which allows me to see all different frames), but I found other ways to see it correctly. Good!

However, I do not see at all the `animation.mp4` file. Where is it saved in your case? In my case I cannot find it…

---

<div class="post-metadata">

**Author:** ![cormullion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cormullion/32/49131_2.png) [@cormullion](https://discourse.julialang.org/u/cormullion)\
**Post date:** [October 12, 2023, 9:27am UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861/8 "2023-10-12T09:27:30Z")

</div>

Indeed Preview is useful for editing GIF files, since it opens a single animated GIF into PNGs of each frame.

Your FFMPEG code writes the output to `/tmp/animation.mp4`.

---

<div class="post-metadata">

**Author:** ![aleppersico](https://avatars.discourse-cdn.com/v4/letter/a/51bf81/32.png) [@aleppersico](https://discourse.julialang.org/u/aleppersico)\
**Post date:** [October 12, 2023, 9:45am UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861/9 "2023-10-12T09:45:57Z")

</div>

Actually, no folder named `tmp` exists in `pwd()` and even if I manually create one, still `animation.mp4` is not saved there.  
I tried also to change the output directory to `/animation.mp4` with

```julia
FFMPEG.ffmpeg_exe(`-r 30 -f image2 -i $(pwd())/%10d.png -c:v libx264 -r 30 -pix_fmt yuv420p -y /animation.mp4`)

```

but then an `ERROR` appears:

```julia
ffmpeg version 4.4.2 Copyright (c) 2000-2021 the FFmpeg developers
  built with clang version 13.0.1 (/home/mose/.julia/dev/BinaryBuilderBase/deps/downloads/clones/llvm-project.git-1df819a03ecf6890e3787b27bfd4f160aeeeeacd50a98d003be8b0893f11a9be 75e33f71c2dae584b13a7d1186ae0a038ba98838)
  configuration: --enable-cross-compile --cross-prefix=/opt/aarch64-apple-darwin20/bin/aarch64-apple-darwin20- --arch=aarch64 --target-os=darwin --cc=cc --cxx=c++ --dep-cc=cc --ar=ar --nm=nm --sysinclude=/workspace/destdir/include --pkg-config=/usr/bin/pkg-config --pkg-config-flags=--static --prefix=/workspace/destdir --sysroot=/opt/aarch64-apple-darwin20/aarch64-apple-darwin20/sys-root --extra-libs=-lpthread --enable-gpl --enable-version3 --enable-nonfree --disable-static --enable-shared --enable-pic --disable-debug --disable-doc --enable-avresample --enable-libaom --enable-libass --enable-libfdk-aac --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libvorbis --enable-libx264 --enable-libx265 --enable-libvpx --enable-encoders --enable-decoders --enable-muxers --enable-demuxers --enable-parsers --enable-openssl --disable-schannel --extra-cflags=-I/workspace/destdir/include --extra-ldflags=-L/workspace/destdir/lib --objcc='cc -x objective-c'
  libavutil 56. 70.100 / 56. 70.100
  libavcodec 58.134.100 / 58.134.100
  libavformat 58. 76.100 / 58. 76.100
  libavdevice 58. 13.100 / 58. 13.100
  libavfilter 7.110.100 / 7.110.100
  libavresample 4. 0. 0 / 4. 0. 0
  libswscale 5. 9.100 / 5. 9.100
  libswresample 3. 9.100 / 3. 9.100
  libpostproc 55. 9.100 / 55. 9.100
Input #0, image2, from '/Users/alessandropersico/Library/Mobile Documents/com~apple~CloudDocs/Blykalla (LeadCold)/Research project/BELLA code/Julia/BELLA_v2-master/%10d.png':
  Duration: 00:00:00.33, start: 0.000000, bitrate: N/A
  Stream #0:0: Video: png, rgb24(pc), 800x800, 30 fps, 30 tbr, 30 tbn, 30 tbc
/animation.mp4: Read-only file system
ERROR: failed process: Process(`/Users/alessandropersico/.julia/artifacts/bf37190b92ac2fc3dd5e7073ff7ec7bbfd10343f/bin/ffmpeg -r 30 -f image2 -i '/Users/alessandropersico/Library/Mobile Documents/com~apple~CloudDocs/Blykalla (LeadCold)/Research project/BELLA code/Julia/BELLA_v2-master/%10d.png' -c:v libx264 -r 30 -pix_fmt yuv420p -y /animation.mp4`, ProcessExited(1)) [1]

Stacktrace:
  [1] pipeline_error
    @ ./process.jl:565 [inlined]
  [2] run(::Cmd; wait::Bool)
    @ Base ./process.jl:480
  [3] run
    @ ./process.jl:477 [inlined]
  [4] (::FFMPEG.var"#4#6"{Cmd})(command_path::String)
    @ FFMPEG ~/.julia/packages/FFMPEG/OUpap/src/FFMPEG.jl:112
  [5] (::JLLWrappers.var"#2#3"{FFMPEG.var"#4#6"{Cmd}, String})()
    @ JLLWrappers ~/.julia/packages/JLLWrappers/QpMQW/src/runtime.jl:49
  [6] withenv(::JLLWrappers.var"#2#3"{FFMPEG.var"#4#6"{Cmd}, String}, ::Pair{String, String}, ::Vararg{Pair{String, String}})
    @ Base ./env.jl:172
  [7] withenv_executable_wrapper(f::Function, executable_path::String, PATH::String, LIBPATH::String, adjust_PATH::Bool, adjust_LIBPATH::Bool)
    @ JLLWrappers ~/.julia/packages/JLLWrappers/QpMQW/src/runtime.jl:48
  [8] invokelatest(::Any, ::Any, ::Vararg{Any}; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Base ./essentials.jl:729
  [9] invokelatest(::Any, ::Any, ::Vararg{Any})
    @ Base ./essentials.jl:726
 [10] ffmpeg(f::Function; adjust_PATH::Bool, adjust_LIBPATH::Bool)
    @ FFMPEG_jll ~/.julia/packages/JLLWrappers/QpMQW/src/products/executable_generators.jl:21
 [11] ffmpeg(f::Function)
    @ FFMPEG_jll ~/.julia/packages/JLLWrappers/QpMQW/src/products/executable_generators.jl:19
 [12] #exe#2
    @ ~/.julia/packages/FFMPEG/OUpap/src/FFMPEG.jl:111 [inlined]
 [13] ffmpeg_exe(args::Cmd)
    @ FFMPEG ~/.julia/packages/FFMPEG/OUpap/src/FFMPEG.jl:123
 [14] top-level scope
    @ ~/Library/Mobile Documents/com~apple~CloudDocs/Blykalla (LeadCold)/Research project/BELLA code/Julia/BELLA_v2-master/Source code/conti vari.jl:2955

```

Any tips here?

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [October 12, 2023, 9:51am UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861/10 "2023-10-12T09:51:03Z")

</div>

`/tmp` means top level, not below `pwd()`

---

<div class="post-metadata">

**Author:** ![cormullion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cormullion/32/49131_2.png) [@cormullion](https://discourse.julialang.org/u/cormullion)\
**Post date:** [October 12, 2023, 9:52am UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861/11 "2023-10-12T09:52:09Z")

</div>

> [@aleppersico](#):
>
> `/animation.mp4: Read-only file system`

I’d try without the `/`. But I think this is a different issue, something to do with how you specify files on your MacBook. I can see why an attempt to write to the root level (`/animation.mp4`) isn’t allowed. You’re working on an iCloud Drive too? That might also be a factor.

---

<div class="post-metadata">

**Author:** ![aleppersico](https://avatars.discourse-cdn.com/v4/letter/a/51bf81/32.png) [@aleppersico](https://discourse.julialang.org/u/aleppersico)\
**Post date:** [October 12, 2023, 10:04am UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861/12 "2023-10-12T10:04:20Z")

</div>

Yes I am working on iCloud Drive, but I have always saved .png, .jl, .whatever successfully, so I can’t see why it does not work this time.

Alright, I just solved it by adding `$(pwd())` before `/animation.mp4`:

```julia
FFMPEG.ffmpeg_exe(`-r 30 -f image2 -i $(pwd())/%10d.png -c:v libx264 -r 30 -pix_fmt yuv420p -y $(pwd())/animation.mp4`)

```

Of course one could add whatever directory instead of `pwd()`.

I just have one last question: why when I specify `/tmp/` (as I wrote before), no file is saved anywhere? Is it related to the fact the `/tmp/` is somehow a special temporary folder which does not exist? Or is it located somewhere that I cannot find?  
This is the command I am talking about:

```julia
FFMPEG.ffmpeg_exe(`-r 30 -f image2 -i $(pwd())/%10d.png -c:v libx264 -r 30 -pix_fmt yuv420p -y /tmp/animation.mp4`)

```

---

<div class="post-metadata">

**Author:** ![cormullion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cormullion/32/49131_2.png) [@cormullion](https://discourse.julialang.org/u/cormullion)\
**Post date:** [October 12, 2023, 10:22am UTC](https://discourse.julialang.org/t/save-animation-with-luxor-jl/104861/13 "2023-10-12T10:22:03Z")

</div>

If you can’t see the `/tmp` directory in a terminal:

```plaintext
❯ ls -l /tmp
lrwxr-xr-x@ 1 root wheel 11 9 Feb 2023 /tmp -> private/tmp

❯ ls /tmp
/tmp/animation.mp4

```

then I’m guessing that there’s something about your Mac setup that’s hiding it… ? I know that Apple like to hide folders like `~/Library` by default, so … 🤷🏻‍♂️
