├── .gitignore ├── README.md ├── animism ├── __init__.py └── animism.py ├── examples └── phasor.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # EasyInstall temporaries 4 | # 5 | 6 | animism.egg-info/ 7 | build/ 8 | dist/ 9 | 10 | # 11 | # Output files 12 | # 13 | 14 | *.mp4 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | animism 2 | ======= 3 | 4 | `animism` is a simple framework for procedurally generating animations with 5 | *cairo* and *ffmpeg*. 6 | 7 | ## Installation 8 | 9 | 1. Install depedencies: 10 | 11 | On Debian/Ubuntu: 12 | ``` 13 | $ sudo apt install python3-setuptools python3-cairo python3-pip 14 | $ sudo pip3 install progressbar2 15 | ``` 16 | 17 | 2. Build the python module: 18 | ``` 19 | $ python3 setup.py build 20 | ``` 21 | 22 | 3. Install the python module: 23 | ``` 24 | $ sudo python3 setup.py install 25 | ``` 26 | 27 | ## Arguments 28 | ``` 29 | usage: test.py [-h] [-p] [-v] [OUT_PATH] 30 | 31 | Render an animation with cairo and ffmpeg 32 | 33 | positional arguments: 34 | OUT_PATH The output file path 35 | 36 | optional arguments: 37 | -h, --help show this help message and exit 38 | -p, --preview Display a preview window 39 | -v, --verbose Print verbose output to stdout 40 | ``` 41 | 42 | ## Example 43 | 44 | ### Code 45 | ``` 46 | #!/usr/bin/env python3 47 | 48 | import animism 49 | import cairo 50 | 51 | def draw_frame(frame_num, width, height): 52 | surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, width, height) 53 | ctx = cairo.Context (surface) 54 | 55 | # Fill background 56 | ctx.rectangle(0, 0, width, height) 57 | ctx.set_source_rgb(1, 1, 1) 58 | ctx.fill () 59 | 60 | # Draw content 61 | offset = frame_num * 2 62 | l = width / 8 63 | r = 7 * width / 8 64 | t = height / 8 65 | b = 7 * height / 8 66 | 67 | ctx.move_to(l + offset, t) 68 | ctx.line_to(r - offset, b) 69 | ctx.move_to(l, t + offset) 70 | ctx.line_to(r, b - offset) 71 | ctx.move_to(r - offset, t) 72 | ctx.line_to(l + offset, b) 73 | ctx.move_to(l, b - offset) 74 | ctx.line_to(r, t + offset) 75 | 76 | ctx.set_source_rgb(0.5, 0, 1) 77 | ctx.set_line_width(height / 64) 78 | ctx.set_line_cap(cairo.LINE_CAP_ROUND) 79 | ctx.stroke() 80 | 81 | return surface 82 | 83 | if __name__ == '__main__': 84 | animism.run(draw_frame, 200) 85 | ``` 86 | 87 | ### Usage 88 | ``` 89 | $ python3 test.py 90 | $ mplayer out.mp4 91 | ``` 92 | 93 | For more examples see `examples/` sub-directory. 94 | -------------------------------------------------------------------------------- /animism/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from .animism import * 4 | -------------------------------------------------------------------------------- /animism/animism.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import multiprocessing 5 | import progressbar 6 | import os 7 | import subprocess as sp 8 | import sys 9 | import tempfile 10 | 11 | FFMPEG_BIN = '/usr/bin/ffmpeg' 12 | 13 | 14 | def make_frame(draw_frame_func, frame_num, width, height): 15 | frame = draw_frame_func(frame_num, width, height) 16 | f, path = tempfile.mkstemp('.png') 17 | os.close(f) 18 | frame.write_to_png(path) 19 | return path 20 | 21 | 22 | def run(draw_frame_func, frame_count, width=1920, height=1080, frame_rate=30): 23 | parser = argparse.ArgumentParser( 24 | description='Render an animation with cairo and ffmpeg') 25 | parser.add_argument('out_path', metavar='OUT_PATH', type=str, nargs='?', 26 | help='The output file path', default='out.mp4') 27 | parser.add_argument('-p', '--preview', action='store_true', 28 | help='Display a preview window') 29 | parser.add_argument('-v', '--verbose', action='store_true', 30 | help='Print verbose output to stdout') 31 | args = parser.parse_args() 32 | 33 | command = [FFMPEG_BIN, 34 | '-y', # (optional) overwrite output file if it exists 35 | '-f', 'image2pipe', 36 | '-r', str(frame_rate), 37 | '-vcodec', 'png', 38 | '-r', str(frame_rate), 39 | '-i', '-', # The imput comes from a pipe 40 | '-vcodec', 'libx264', 41 | '-r', str(frame_rate), 42 | args.out_path] + ([ 43 | '-vcodec', 'rawvideo', 44 | '-pix_fmt', 'yuyv422', 45 | '-window_size', '%dx%d' % (width/2, height/2), 46 | '-f', 'sdl', 'Preview' 47 | ] if args.preview else []) 48 | 49 | pool = multiprocessing.Pool(multiprocessing.cpu_count()) 50 | frames = [pool.apply_async(make_frame, (draw_frame_func, f, width, height)) 51 | for f in range(frame_count)] 52 | 53 | p = sp.Popen(command, stdin=sp.PIPE, 54 | stdout=None if args.verbose else sp.PIPE, stderr=sp.STDOUT) 55 | bar = progressbar.ProgressBar(max_value = frame_count) 56 | 57 | try: 58 | for frame_num in range(frame_count): 59 | png_path = frames[frame_num].get() 60 | with open(png_path, 'rb') as f: 61 | p.stdin.write(f.read()) 62 | p.stdin.flush() 63 | os.remove(png_path) 64 | bar.update(frame_num) 65 | p.stdin.close() 66 | p.wait() 67 | except Exception as e: 68 | print(e) 69 | pass 70 | -------------------------------------------------------------------------------- /examples/phasor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import animism 4 | import cairo 5 | import math 6 | 7 | def arrow_head(ctx): 8 | ctx.move_to(0, 0) 9 | ctx.line_to(-2, -.5) 10 | ctx.rel_curve_to(.125, .25, .125, .75, 0, 1) 11 | ctx.line_to(0, 0) 12 | 13 | def arrow(ctx, s, e, arrow_size=20): 14 | d = (e[0] - s[0], e[1] - s[1]) 15 | l = math.sqrt(d[0] ** 2 + d[1] ** 2) 16 | u = (d[0] / l, d[1] / l) 17 | r = (arrow_size * u[0], arrow_size * u[1]) 18 | 19 | ctx.save() 20 | 21 | ctx.move_to(*s) 22 | line_length = l - arrow_size 23 | ctx.line_to(s[0] + u[0] * line_length, s[1] + u[1] * line_length) 24 | ctx.stroke_preserve() 25 | 26 | ctx.set_matrix(cairo.Matrix(r[0], r[1], -r[1], r[0], *e)) 27 | arrow_head(ctx) 28 | ctx.fill() 29 | 30 | ctx.restore() 31 | 32 | def draw_frame(t, width, height): 33 | surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, width, height) 34 | ctx = cairo.Context (surface) 35 | 36 | ctx.rectangle(0, 0, width, height) 37 | ctx.set_source_rgb(1, 1, 1) 38 | ctx.fill () 39 | 40 | o = (height/2, height/2) 41 | d = 360 42 | axis_length = d * 1.2 43 | x_history = int(o[0] + axis_length * 1.1) 44 | blob_radius = 10 45 | 46 | f = 0.004 47 | 48 | phi = t * f * math.pi * 2 49 | p = (o[0] + d * math.cos(phi), o[0] - d * math.sin(phi)) 50 | 51 | # Draw the axes 52 | ctx.set_source_rgb(0.75, 0.75, 0.75) 53 | ctx.set_line_width(5) 54 | arrow(ctx, (o[0] - axis_length, o[1]), (o[0] + axis_length, o[1])) 55 | arrow(ctx, (o[0], o[1] + axis_length), (o[0], o[1] - axis_length)) 56 | 57 | # Draw the link-line 58 | ctx.move_to(*p) 59 | ctx.line_to(x_history, p[1]) 60 | ctx.save() 61 | ctx.set_dash([10, 10]) 62 | ctx.stroke() 63 | ctx.restore() 64 | 65 | # Draw the phasor 66 | ctx.set_source_rgb(0, 0, 0) 67 | ctx.set_line_width(5) 68 | ctx.arc(*p, blob_radius, 0, math.pi * 2) 69 | ctx.fill() 70 | arrow(ctx, o, (o[0] + d * math.cos(phi), o[0] - d * math.sin(phi))) 71 | 72 | # Draw the wave-history 73 | for x in range(x_history, width + 10): 74 | phi = (t - x + x_history) * f * math.pi * 2 75 | ctx.line_to(x, o[0] - d * math.sin(phi)) 76 | ctx.set_source_rgb(0, 0, 0) 77 | ctx.stroke() 78 | 79 | return surface 80 | 81 | 82 | if __name__ == '__main__': 83 | animism.run(draw_frame, 200) 84 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='animism', 5 | version='0.1', 6 | packages=find_packages(), 7 | 8 | install_requires=['progressbar2>=3.18.0'], 9 | 10 | author='Joel Holdsworth', 11 | author_email='joel@airwebreathe.org.uk', 12 | description='A package for rendering animations programatically', 13 | license='GPLv2', 14 | url='http://github.com/jhol/animism' 15 | ) 16 | --------------------------------------------------------------------------------