diff --git a/README.md b/README.md
index 05fae52..b2a8571 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,70 @@
# jupyter-manim
-%%mainm cell magic for IPython/Jupyter to show the output video
+[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://choosealicense.com/licenses/mit/)
+
+
+Integrates [manim](https://github.com/3b1b/manim) (animation engine for explanatory math videos)
+with Jupyter displaying the resulting video when using `%%manim` cell magic to wrap a scene definition.
+
+### Quick preview
+
+
+
+The code in the example above comes from the excellent [manim tutorial](https://github.com/malhotra5/Manim-Tutorial).
+
+### Installation
+
+```sh
+pip3 install jupyter-manim
+```
+
+### Usage
+
+Your arguments will be passed to manim, exactly as if these were command line options.
+
+For example, to render scene defined with class `Shapes(Scene)` use
+
+```python
+%%manim Shapes
+from manimlib.scene.scene import Scene
+from manimlib.mobject.geometry import Circle
+from manimlib.animation.creation import ShowCreation
+
+class Shapes(Scene):
+
+ def construct(self):
+ circle = Circle()
+ self.play(ShowCreation(circle))
+```
+
+NOTE: currently the code has to be self-contained as it will be run in a separate namespace.
+Thus, all the imports have to be contained in your cell.
+
+In future, an option to export the current namespace (or specific variables) will be added.
+It could be implemented by pickling the Python locals and globals and then pre-pending the cell with an un-pickling script (PRs welcome!).
+
+In the latest version of manimlib (not yet released) you will be able to import everything at once using:
+
+```python
+from manimlib.imports import *
+```
+
+
+To display manim help and options use:
+
+```
+%%manim -h
+pass
+```
+
+
+
+The `%%manim` magic (by default) hides the progress bars as well as other logging messages generated by manim.
+You can disable this behaviour using `--verbose` flag
+
+#### Video player control options
+
+ - `--no-control` - hides the controls
+ - `--no-autoplay` - disables the autoplay feature
+ - `-r` or `--resolution` - control the height and width of the video player;
+ this option is shared with manim and requires the resolution in following format:
+ `height,width`, e.g. `%%manim Shapes -r 200,1000`
diff --git a/jupyter_manim/__init__.py b/jupyter_manim/__init__.py
new file mode 100644
index 0000000..2426ce8
--- /dev/null
+++ b/jupyter_manim/__init__.py
@@ -0,0 +1,155 @@
+from IPython.core.magic import Magics, magics_class, cell_magic
+from unittest.mock import patch
+from tempfile import NamedTemporaryFile
+import manimlib
+from IPython.display import HTML
+import sys
+from io import StringIO
+from contextlib import ExitStack, suppress, redirect_stdout, redirect_stderr
+from warnings import warn
+from IPython import get_ipython
+from pathlib import Path
+
+std_out = sys.stdout
+
+
+def video(path, width=854, height=480, controls=True, autoplay=True):
+ return HTML(f"""
+
+ """)
+
+
+class StringIOWithCallback(StringIO):
+
+ def __init__(self, callback, **kwargs):
+ super().__init__(**kwargs)
+ self.callback = callback
+
+ def write(self, s):
+ super().write(s)
+ self.callback(s)
+
+
+@magics_class
+class ManimMagics(Magics):
+ path_line_start = 'File ready at '
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.defaults = {
+ 'autoplay': True,
+ 'controls': True,
+ 'silent': True,
+ 'width': 854,
+ 'height': 480
+ }
+
+ video_settings = {'width', 'height', 'controls', 'autoplay'}
+ magic_off_switches = {
+ 'verbose': 'silent',
+ 'no-controls': 'controls',
+ 'no-autoplay': 'autoplay'
+ }
+
+ @cell_magic
+ def manim(self, line, cell):
+ # execute the code - won't generate any video, however it will introduce
+ # the variables into the notebook's namespace (enabling autocompletion etc);
+ # this also allows users to get feedback on some code errors early on
+ get_ipython().ex(cell)
+
+ user_args = line.split(' ')
+
+ # path of the output video
+ path = None
+
+ settings = self.defaults.copy()
+
+ # disable the switches as indicated by the user
+ for key, arg in self.magic_off_switches.items():
+ if '--' + key in user_args:
+ user_args.remove('--' + key)
+ settings[arg] = False
+
+ resolution_index = (
+ user_args.index('-r') if '-r' in user_args else
+ user_args.index('--resolution') if '--resolution' in user_args else
+ None
+ )
+ if resolution_index is not None:
+ # the resolution is passed as "height,width"
+ try:
+ h, w = user_args[resolution_index + 1].split(',')
+ settings['height'] = h
+ settings['width'] = w
+ except (IndexError, KeyError):
+ warn('Unable to retrieve dimensions from your resolution setting, falling back to the defaults')
+
+ silent = settings['silent']
+
+ def catch_path_and_forward(lines):
+ nonlocal path
+ for line in lines.split('\n'):
+ if not silent:
+ print(line, file=std_out)
+
+ if line.startswith(self.path_line_start):
+ path = line[len(self.path_line_start):].strip()
+
+ with NamedTemporaryFile('w', suffix='.py') as f:
+ f.write(cell)
+ f.flush()
+
+ args = ['manim', f.name, *user_args]
+
+ stdout = StringIOWithCallback(catch_path_and_forward)
+
+ with ExitStack() as stack:
+
+ enter = stack.enter_context
+
+ enter(patch.object(sys, 'argv', args))
+ enter(suppress(SystemExit))
+ enter(redirect_stdout(stdout))
+
+ if silent:
+ stderr = StringIO()
+ enter(redirect_stderr(stderr))
+
+ manimlib.main()
+
+ if path:
+ path = Path(path)
+ assert path.exists()
+
+ # To display a video in Jupyter, we need to have access to it
+ # so it has to be within the working tree. The absolute paths
+ # are usually outside of the accessible range.
+ relative_path = path.relative_to(Path.cwd())
+
+ video_settings = {
+ k: v
+ for k, v in settings.items()
+ if k in self.video_settings
+ }
+
+ return video(relative_path, **video_settings)
+ else:
+ warn('Could not find path in the manim output')
+
+ # If we were silent, some errors could have been silenced too.
+ if silent:
+ # Let's break the silence:
+ print(stdout.getvalue())
+ print(stderr.getvalue(), file=sys.stderr)
+
+
+ip = get_ipython()
+ip.register_magics(ManimMagics)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..8d85f5c
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+IPython
+manimlib
diff --git a/screenshots/cell_magic_demo.png b/screenshots/cell_magic_demo.png
new file mode 100644
index 0000000..98f23ec
Binary files /dev/null and b/screenshots/cell_magic_demo.png differ
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..b88034e
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[metadata]
+description-file = README.md
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..f2ed8db
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,49 @@
+from setuptools import setup
+from setuptools import find_packages
+
+
+try:
+ from pypandoc import convert
+
+ def get_long_description(file_name):
+ return convert(file_name, 'rst', 'md')
+
+except ImportError:
+
+ def get_long_description(file_name):
+ with open(file_name) as f:
+ return f.read()
+
+
+if __name__ == '__main__':
+ setup(
+ name='jupyter_manim',
+ packages=find_packages(),
+ version='0.1',
+ license='MIT',
+ description='Cell magic rendering displaying videos in Jupyter/IPython',
+ long_description=get_long_description('README.md'),
+ author='Michal Krassowski',
+ author_email='krassowski.michal+pypi@gmail.com',
+ url='https://github.com/krassowski/jupyter-manim',
+ download_url='https://github.com/krassowski/jupyter-manim/tarball/v0.1',
+ keywords=['jupyter', 'jupyterlab', 'notebook', 'manim', 'manimlib'],
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'License :: OSI Approved :: MIT License',
+ 'Framework :: IPython',
+ 'Framework :: Jupyter',
+ 'Operating System :: Microsoft :: Windows',
+ 'Operating System :: POSIX :: Linux',
+ 'Topic :: Utilities',
+ 'Topic :: Software Development :: User Interfaces',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Science/Research',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7'
+ ],
+ install_requires=[
+ 'manimlib', 'IPython'
+ ],
+ )