ManimCommunity / manim

A community-maintained Python framework for creating mathematical animations.
https://www.manim.community
MIT License
21.18k stars 1.54k forks source link

The `-a` flag makes Manim crash #1108

Closed aebkea closed 3 years ago

aebkea commented 3 years ago

Description of bug / unexpected behavior

When using either the -a flag or when providing a comma separated list of numbers whose corresponding scenes should be rendered, Manim crashes.

Expected behavior

I expect that Manim should cleanly render all files then quit.

How to reproduce the issue

Code for reproducing the problem ```py # filename: example.py from manim import * class WriteScene1(Scene): def construct(self): text = Tex("Alice and Bob").scale(3) self.play(Write(text)) class WriteScene2(Scene): def construct(self): text = Tex("Alpha and Beta").scale(3) self.play(Write(text)) ```

Additional media files

Images/GIFs

Logs

Terminal output ``` {"levelname": "INFO", "module": "logger_utils", "message": "Log file will be saved in <>"} {"levelname": "DEBUG", "module": "hashing", "message": "Hashing ..."} {"levelname": "DEBUG", "module": "hashing", "message": "Hashing done in <> s."} {"levelname": "DEBUG", "module": "hashing", "message": "Hash generated : <>"} {"levelname": "INFO", "module": "caching", "message": "Animation 0 : Using cached data (hash : <>)"} {"levelname": "DEBUG", "module": "caching", "message": "List of the first few animation hashes of the scene: <>"} {"levelname": "DEBUG", "module": "scene_file_writer", "message": "Partial movie files to combine (1 files): <>"} {"levelname": "INFO", "module": "scene_file_writer", "message": "\nFile ready at <>\n"} {"levelname": "INFO", "module": "scene", "message": "Rendered WriteScene1\nPlayed 1 animations"} ``` ``` (env_Manim_dev) PS C:\Users\Abel\Manim\dev\Example> manim .\example.py -a -qh -v DEBUG --log_to_file Manim Community v0.4.0 [03/14/21 16:09:32] INFO Log file will be saved in media\logs\example.log logger_utils.py:175 DEBUG Hashing ... hashing.py:239 [03/14/21 16:09:33] DEBUG Hashing done in 0.029161 s. hashing.py:252 DEBUG Hash generated : 450974505_815064119_2235554292 hashing.py:256 INFO Animation 0 : Using cached data (hash : 450974505_815064119_2235554292) caching.py:39 DEBUG List of the first few animation hashes of the scene: ['450974505_815064119_2235554292'] caching.py:48 DEBUG Partial movie files to combine (1 files): ['C:\\Users\\Abel\\Manim\\dev\\Example\\media\\videos\\example\\1080p60\\partial_movie_files\\WriteScene1\\450974505_815064119_2235554292.mp4'] scene_file_writer.py:447 INFO scene_file_writer.py:579 File ready at C:\Users\Abel\Manim\dev\Example\media\videos\example\1080p60\WriteScene1.mp4 INFO Rendered WriteScene1 scene.py:176 Played 1 animations ``` ``` Traceback (most recent call last): ╭──────────────────────────────────────────────────────────────────────────────────────╮ │ File "c:\users\abel\manim\dev\env_manim_dev\lib\site-packages\manim\__main__.py", │ │line 103, in main │ │ 100 else: │ │ 101 for SceneClass in scene_classes_from_file(input_file): │ │ 102 try: │ │ ❱ 103 scene = SceneClass() │ │ 104 scene.render() │ │ 105 open_file_if_needed(scene.renderer.file_writer) │ │ 106 except Exception: │ │ File "c:\users\abel\manim\dev\env_manim_dev\lib\site-packages\manim\scene\scene.py", │ │line 91, in __init__ │ │ 88 ) │ │ 89 else: │ │ 90 self.renderer = renderer │ │ ❱ 91 self.renderer.init_scene(self) │ │ 92 │ │ 93 self.mobjects = [] │ │ 94 # TODO, remove need for foreground mobjects │ │ File "c:\users\abel\manim\dev\env_manim_dev\lib\site-packages\manim\renderer\cairo_re│ │nderer.py", line 69, in init_scene │ │ 66 self.static_image = None │ │ 67 │ │ 68 def init_scene(self, scene): │ │ ❱ 69 self.file_writer = SceneFileWriter( │ │ 70 self, │ │ 71 scene.__class__.__name__, │ │ 72 ) │ │ File "c:\users\abel\manim\dev\env_manim_dev\lib\site-packages\manim\scene\scene_file_│ │writer.py", line 48, in __init__ │ │ 45 def __init__(self, renderer, scene_name, **kwargs): │ │ 46 self.renderer = renderer │ │ 47 self.stream_lock = False │ │ ❱ 48 self.init_output_directories(scene_name) │ │ 49 self.init_audio() │ │ 50 self.frame_count = 0 │ │ 51 self.partial_movie_files = [] │ │ File "c:\users\abel\manim\dev\env_manim_dev\lib\site-packages\manim\scene\scene_file_│ │writer.py", line 102, in init_output_directories │ │ 99 add_extension_if_not_present(default_name, GIF_FILE_EXTENS│ │ 100 ) │ │ 101 │ │ ❱ 102 self.partial_movie_directory = guarantee_existence( │ │ 103 config.get_dir( │ │ 104 "partial_movie_dir", │ │ 105 scene_name=default_name, │ │ File │ │"c:\users\abel\manim\dev\env_manim_dev\lib\site-packages\manim\utils\file_ops.py", │ │line 36, in guarantee_existence │ │ 33 │ │ 34 def guarantee_existence(path): │ │ 35 if not os.path.exists(path): │ │ ❱ 36 os.makedirs(path) │ │ 37 return os.path.abspath(path) │ │ 38 │ │ 39 │ │ File "C:\Users\Abel\AppData\Local\Programs\Python\Python39\lib\os.py", line 215, in │ │makedirs │ │ 212 head, tail = path.split(head) │ │ 213 if head and tail and not path.exists(head): │ │ 214 try: │ │ ❱ 215 makedirs(head, exist_ok=exist_ok) │ │ 216 except FileExistsError: │ │ 217 # Defeats race condition when another thread created the path │ │ 218 pass │ │ File "C:\Users\Abel\AppData\Local\Programs\Python\Python39\lib\os.py", line 215, in │ │makedirs │ │ 212 head, tail = path.split(head) │ │ 213 if head and tail and not path.exists(head): │ │ 214 try: │ │ ❱ 215 makedirs(head, exist_ok=exist_ok) │ │ 216 except FileExistsError: │ │ 217 # Defeats race condition when another thread created the path │ │ 218 pass │ │ File "C:\Users\Abel\AppData\Local\Programs\Python\Python39\lib\os.py", line 215, in │ │makedirs │ │ 212 head, tail = path.split(head) │ │ 213 if head and tail and not path.exists(head): │ │ 214 try: │ │ ❱ 215 makedirs(head, exist_ok=exist_ok) │ │ 216 except FileExistsError: │ │ 217 # Defeats race condition when another thread created the path │ │ 218 pass │ │ File "C:\Users\Abel\AppData\Local\Programs\Python\Python39\lib\os.py", line 215, in │ │makedirs │ │ 212 head, tail = path.split(head) │ │ 213 if head and tail and not path.exists(head): │ │ 214 try: │ │ ❱ 215 makedirs(head, exist_ok=exist_ok) │ │ 216 except FileExistsError: │ │ 217 # Defeats race condition when another thread created the path │ │ 218 pass │ │ File "C:\Users\Abel\AppData\Local\Programs\Python\Python39\lib\os.py", line 215, in │ │makedirs │ │ 212 head, tail = path.split(head) │ │ 213 if head and tail and not path.exists(head): │ │ 214 try: │ │ ❱ 215 makedirs(head, exist_ok=exist_ok) │ │ 216 except FileExistsError: │ │ 217 # Defeats race condition when another thread created the path │ │ 218 pass │ │ File "C:\Users\Abel\AppData\Local\Programs\Python\Python39\lib\os.py", line 215, in │ │makedirs │ │ 212 head, tail = path.split(head) │ │ 213 if head and tail and not path.exists(head): │ │ 214 try: │ │ ❱ 215 makedirs(head, exist_ok=exist_ok) │ │ 216 except FileExistsError: │ │ 217 # Defeats race condition when another thread created the path │ │ 218 pass │ │ File "C:\Users\Abel\AppData\Local\Programs\Python\Python39\lib\os.py", line 215, in │ │makedirs │ │ 212 head, tail = path.split(head) │ │ 213 if head and tail and not path.exists(head): │ │ 214 try: │ │ ❱ 215 makedirs(head, exist_ok=exist_ok) │ │ 216 except FileExistsError: │ │ 217 # Defeats race condition when another thread created the path │ │ 218 pass │ │ File "C:\Users\Abel\AppData\Local\Programs\Python\Python39\lib\os.py", line 215, in │ │makedirs │ │ 212 head, tail = path.split(head) │ │ 213 if head and tail and not path.exists(head): │ │ 214 try: │ │ ❱ 215 makedirs(head, exist_ok=exist_ok) │ │ 216 except FileExistsError: │ │ 217 # Defeats race condition when another thread created the path │ │ 218 pass │ │ File "C:\Users\Abel\AppData\Local\Programs\Python\Python39\lib\os.py", line 215, in │ │makedirs │ │ 212 head, tail = path.split(head) │ │ 213 if head and tail and not path.exists(head): │ │ 214 try: │ │ ❱ 215 makedirs(head, exist_ok=exist_ok) │ │ 216 except FileExistsError: │ │ 217 # Defeats race condition when another thread created the path │ │ 218 pass │ │ File "C:\Users\Abel\AppData\Local\Programs\Python\Python39\lib\os.py", line 215, in │ │makedirs │ │ 212 head, tail = path.split(head) │ │ 213 if head and tail and not path.exists(head): │ │ 214 try: │ │ ❱ 215 makedirs(head, exist_ok=exist_ok) │ │ 216 except FileExistsError: │ │ 217 # Defeats race condition when another thread created the path │ │ 218 pass │ │ File "C:\Users\Abel\AppData\Local\Programs\Python\Python39\lib\os.py", line 225, in │ │makedirs │ │ 222 if tail == cdir: # xxx/newdir/. exists if xxx/newdir exists│ │ 223 return │ │ 224 try: │ │ ❱ 225 mkdir(name, mode) │ │ 226 except OSError: │ │ 227 # Cannot rely on checking for EEXIST, since the operating system │ │ 228 # could give priority to other errors like EACCES or EROFS │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ OSError: [WinError 123] The filename, directory name, or volume label syntax is incorrect: 'media\\videos\\example\\1080p60\\partial_movie_files\\C:' ```

System specifications

System Details - OS (with version, e.g Windows 10 v2004 or macOS 10.15 (Catalina)): Windows 10 v1909 - RAM: 32GB 3200MHz - Python version (`python/py/python3 --version`): 3.9.2 - Installed modules (provide output from `pip list`): ``` Package Version ----------------------------- ---------- alabaster 0.7.12 appdirs 1.4.4 atomicwrites 1.4.0 attrs 20.3.0 Babel 2.9.0 beautifulsoup4 4.9.3 black 20.8b1 cachetools 4.2.1 certifi 2020.12.5 chardet 4.0.0 click 7.1.2 colorama 0.4.4 colour 0.1.5 commonmark 0.9.1 decorator 4.4.2 docutils 0.16 glcontext 2.3.3 google 3.0.0 google-api-core 1.26.1 google-api-python-client 2.0.2 google-auth 1.27.1 google-auth-httplib2 0.1.0 googleapis-common-protos 1.53.0 grpcio 1.36.1 guzzle-sphinx-theme 0.7.11 httplib2 0.19.0 idna 2.10 imagesize 1.2.0 importlib-metadata 3.7.2 iniconfig 1.1.1 Jinja2 2.11.3 manim 0.4.0 ManimPango 0.2.4 mapbox-earcut 0.12.10 MarkupSafe 1.1.1 moderngl 5.6.4 moderngl-window 2.3.0 multipledispatch 0.6.0 mypy-extensions 0.4.3 networkx 2.5 numpy 1.20.1 packaging 20.9 pathspec 0.8.1 Pillow 8.1.1 pip 21.0.1 pluggy 0.13.1 protobuf 3.15.5 py 1.10.0 pyasn1 0.4.8 pyasn1-modules 0.2.8 pycairo 1.20.0 pydub 0.24.1 pyglet 1.5.15 Pygments 2.8.0 pyparsing 2.4.7 pyrr 0.10.3 pytest 6.2.2 pytz 2021.1 recommonmark 0.7.1 regex 2020.11.13 requests 2.25.1 rich 6.2.0 rsa 4.7.2 scipy 1.6.1 setuptools 49.2.1 six 1.15.0 snowballstemmer 2.1.0 soupsieve 2.2 Sphinx 3.1.2 sphinx-copybutton 0.3.1 sphinxcontrib-applehelp 1.0.2 sphinxcontrib-devhelp 1.0.2 sphinxcontrib-htmlhelp 1.0.3 sphinxcontrib-jsmath 1.0.1 sphinxcontrib-qthelp 1.0.3 sphinxcontrib-serializinghtml 1.1.4 sphinxext-opengraph 0.4.1 toml 0.10.2 tqdm 4.58.0 typed-ast 1.4.2 typing-extensions 3.7.4.3 uritemplate 3.0.1 urllib3 1.26.3 zipp 3.4.1 ```
LaTeX details + LaTeX distribution (e.g. TeX Live 2020): MiKTeX latest + Installed LaTeX packages: all of them
FFMPEG Output of `ffmpeg -version`: ``` ffmpeg version 4.3.2-2021-02-27-full_build-www.gyan.dev Copyright (c) 2000-2021 the FFmpeg developers built with gcc 10.2.0 (Rev6, Built by MSYS2 project) configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-lzma --enable-libsnappy --enable-zlib --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 --enable-libdav1d --enable-libzvbi --enable-librav1e --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-d3d11va --enable-dxva2 --enable-libmfx --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libilbc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint libavutil 56. 51.100 / 56. 51.100 libavcodec 58. 91.100 / 58. 91.100 libavformat 58. 45.100 / 58. 45.100 libavdevice 58. 10.100 / 58. 10.100 libavfilter 7. 85.100 / 7. 85.100 libswscale 5. 7.100 / 5. 7.100 libswresample 3. 7.100 / 3. 7.100 libpostproc 55. 7.100 / 55. 7.100 ```

Additional comments

I tested this both on the latest master branch and on version 0.3.0. Both exhibited the issue.

kilacoda-old commented 3 years ago

Can reproduce. I tracked the problem down and it seems to be caused due to these lines: https://github.com/ManimCommunity/manim/blob/776067584e6365db1898d23a2dc7fe20cf07ae5e/manim/scene/scene_file_writer.py#L71-L75

Apparently, in a later section of the same function, this default_name parameter is passed to guarantee_existence (and subsequently os.makedirs: https://github.com/ManimCommunity/manim/blob/776067584e6365db1898d23a2dc7fe20cf07ae5e/manim/scene/scene_file_writer.py#L102-L109

...which resulted in a path like this: image

instead of something like this: image

Since I don't really know the reasoning behind L74 of SceneFileWriter, pinging @leotrs (last modifier according to blame) for clarity on this and whether changing it would break anything.

kilacoda-old commented 3 years ago

Update: apparently, changing scene_name=default_name to scene_name=scene_name fixes the problem.

aebkea commented 3 years ago

Update: apparently, changing scene_name=default_name to scene_name=scene_name fixes the problem.

@kilacoda This allows the render to complete without error, and the partial movie files are rendered correctly in their respective directories, but all of the combined output files take the filename of the first one rendered out. It seems that default_name is not updated in between each scene. I'm trying to track down the issue with limited success.

edit: After remembering the most tried and true method for debugging, print(), I've determined that config["output_file"] is automatically set to the path of the first rendered file for all subsequent scenes.