From b9e1625425be2dd3bf108ade64a26949cdd239f4 Mon Sep 17 00:00:00 2001 From: dave Date: Sun, 20 Nov 2022 14:48:52 -0800 Subject: [PATCH] test code --- dvd.png | Bin 0 -> 6110 bytes pyanimate.py | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++ test.py | 59 ++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 dvd.png create mode 100644 pyanimate.py create mode 100644 test.py diff --git a/dvd.png b/dvd.png new file mode 100644 index 0000000000000000000000000000000000000000..557d5baa40b62b5166e23949289c1695ae8d43f8 GIT binary patch literal 6110 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 literal 0 HcmV?d00001 diff --git a/pyanimate.py b/pyanimate.py new file mode 100644 index 0000000..064cdd3 --- /dev/null +++ b/pyanimate.py @@ -0,0 +1,124 @@ +import os +from PIL import Image +import tempfile + + +class Video(object): + def __init__(self, framerate, width, height): + self.framerate = framerate + self.width = width + self.height = height + self.sprites = [] + + def add_sprite(self, sprite): + self.sprites.append(sprite) + + def render(self, outpath): + """ + render the video to a file + + for frame in frames: + canvas = image() + for sprite in sprites: + sprite.next_frame() + sprite.draw(canvas) + canvas.save(...) + + ffmepeg() + """ + + with tempfile.TemporaryDirectory() as tmpdir: + for framenum in range(0, 30): + canvas = Image.new('RGB', (self.width, self.height, )) + for sprite in self.sprites: + sprite.draw(canvas) + + with open(os.path.join(tmpdir, "frame_{:08d}.png".format(framenum)), "wb") as f: + canvas.save(f, "PNG") + + for sprite in self.sprites: + sprite.next_frame() + + print("done!") + os.system("open {}".format(tmpdir)) + input() + + + +class Source(object): + def __init__(self, file_path): + self.file_path = file_path + + def load(self): + """ + load the media from disk + """ + raise NotImplementedError() + + +class Sprite(object): + def __init__(self, source, position=(0, 0)): + self.source = source + self.position = position + self.visible = False + self.animations = [] + + def add_animation(self, animation): + self.animations.append(animation) + + #TODO transforms that can modify the image e.g. scale it + # also, AddTransformAnimation/RemoveTransformAnimation to make modifications on the fly + # also, TweenTransformationAnimation + # def add_transform(self, transform): + # pass + + def get_pending_animations(self): + return self.animations#TODO + + def next_frame(self): + for animation in self.get_pending_animations(): + animation.apply() + + def draw(self, canvas): + """ + Draw the sprite on top of the given canvas + """ + raise NotImplementedError() + + +def ImageSprite(Sprite): + def __init__(self, source, position=(0, 0)): + raise Exception("huh") + # super().__init__(source, position) + + def draw(self, canvas): + """ + Draw the sprite on top of the given canvas + """ + raise NotImplementedError() + + +class Animation(object): + # def __init__(self, path=None, filters=None, transforms=None, after=None): + # pass + + def apply(self, sprite): + raise NotImplementedError() + + +class ImageSource(Source): + def load(self): + self.img = Image.open(self.file_path) + + +class VideoSource(Source): + pass + + +class ShowAnimation(Animation): + def apply(self, sprite): + sprite.visible = True + +class HideAnimation(Animation): + def apply(self, sprite): + sprite.visible = False diff --git a/test.py b/test.py new file mode 100644 index 0000000..c8b65cb --- /dev/null +++ b/test.py @@ -0,0 +1,59 @@ +# python-based animation software + +# describe an animation using python code then render it to a video, gif, etc + +# tl;dr we draw PNGs and assemble them with ffmpeg + +# data model: +# source - input media such as a still image or a video +# sprite - an instance of a source's media + +# animation - description of how a source is used in the video + + +# example video of the dvd bounce logo + +from pyanimate import Video, \ + Source, ImageSource, \ + Sprite, ImageSprite, \ + Animation, ShowAnimation, HideAnimation + + +# the video is the canvas that we'll animate objects on top of +video = Video(framerate=30, width=640, height=480) + +# a Source is a piece of media that we can include in the animation +# there should not be any reason to create two Source()s of the same input media. +logo_img = ImageSource("dvd_logo.png") + +# a Sprite is an instance of a piece of media. They contain contextual information such as the position of the sprite +# on the canvas. +logo = ImageSprite(logo_img) #, position=(0, 0)) + + +# now the sprite appears at 0,0 +video.add_sprite(logo) + + +# Animations are things that happen to sprites. Sprites are hidden by default so you need a ShowAnimation() to +# make them visible +logo.add_animation(ShowAnimation()) + +# Now lets make the logo move. In this case, we just move it diagonally +# logo_bounce_1 = Animation( +# # path=xxx, +# # filters=xxx, +# # transforms=xxx, +# # ... +# after=None, +# ) + +# Animations are added to sprites. When the video is rendered, animations are applied to the sprite in each frame. +# Animations begin on frame 0 by default, unless they are delayed by setting a start frame=... or after=.... +# logo.add_animation(logo_bounce_1) + + +# output the final thing +video.render("output.mp4") +# video.render("output.gif") +