First implementation of Camera to replace disp

This commit is contained in:
Grant Sanderson
2016-02-23 22:29:32 -08:00
parent 19e3c7f849
commit c4c342c406
5 changed files with 419 additions and 269 deletions

200
camera.py Normal file
View File

@ -0,0 +1,200 @@
import numpy as np
import itertools as it
import os
import sys
from PIL import Image
import cv2
from colour import Color
import progressbar
from helpers import *
class Camera(object):
DEFAULT_CONFIG = {
#background of a different shape will overwrite these
"pixel_width" : DEFAULT_WIDTH,
"pixel_height" : DEFAULT_HEIGHT,
"background_color" : BLACK,
#
"space_height" : SPACE_HEIGHT,
"space_center" : ORIGIN,
}
def __init__(self, background = None, **kwargs):
digest_config(self, kwargs, locals())
self.init_background()
self.reset()
width_to_height = float(self.pixel_width) / self.pixel_height
self.space_width = self.space_height * width_to_height
def init_background(self):
if self.background:
shape = self.background.shape[:2]
self.pixel_height, self.pixel_width = shape
else:
background_color = Color(self.background_color)
background_rgb = (255*np.array(
background_color.get_rgb()
)).astype('uint8')
ones = np.ones(
(self.pixel_height, self.pixel_width, 1),
dtype = 'uint8'
)
self.background = np.dot(
ones, background_rgb.reshape((1, 3))
)
def get_image(self):
return np.array(self.pixel_array)
def set_image(self, pixel_array):
self.pixel_array = np.array(pixel_array)
def reset(self):
self.set_image(np.array(self.background))
# def paint_region(region, image_array = None, color = None):
# pixels = get_pixels(image_array)
# assert region.shape == pixels.shape[:2]
# if color is None:
# #Random dark color
# rgb = 0.5 * np.random.random(3)
# else:
# rgb = np.array(Color(color).get_rgb())
# pixels[region.bool_grid] = (255*rgb).astype('uint8')
# return pixels
def capture_mobject(self, mobject):
return self.capture_mobjects([mobject])
def capture_mobjects(self, mobjects, include_sub_mobjects = True):
if include_sub_mobjects:
all_families = [
mob.submobject_family()
for mob in mobjects
]
mobjects = reduce(op.add, all_families, [])
for mobject in mobjects:
self.display_points(
mobject.points, mobject.rgbs,
self.adjusted_thickness(mobject.point_thickness)
)
def display_points(self, points, rgbs, thickness):
if len(points) == 0:
return
points = self.align_points_to_camera(points)
pixel_coords = self.points_to_pixel_coords(points)
pixel_coords = self.thickened_coordinates(
pixel_coords, thickness
)
rgbs = (255*rgbs).astype('uint8')
target_len = len(pixel_coords)
factor = target_len/len(rgbs)
rgbs = np.array([rgbs]*factor).reshape((target_len, 3))
on_screen_indices = self.on_screen_pixels(pixel_coords)
pixel_coords = pixel_coords[on_screen_indices]
rgbs = rgbs[on_screen_indices]
flattener = np.array([1, self.pixel_width], dtype = 'int')
flattener = flattener.reshape((2, 1))
indices = np.dot(pixel_coords, flattener)[:,0]
indices = indices.astype('int')
pw, ph = self.pixel_width, self.pixel_height
# new_array = np.zeros((pw*ph, 3), dtype = 'uint8')
# new_array[indices, :] = rgbs
new_pa = self.pixel_array.reshape((ph*pw, 3))
new_pa[indices] = rgbs
self.pixel_array = new_pa.reshape((ph, pw, 3))
def align_points_to_camera(self, points):
## This is where projection should live
return points - self.space_center
def points_to_pixel_coords(self, points):
result = np.zeros((len(points), 2))
width_mult = self.pixel_width/self.space_width/2
width_add = self.pixel_width/2
height_mult = self.pixel_height/self.space_height/2
height_add = self.pixel_height/2
#Flip on y-axis as you go
height_mult *= -1
result[:,0] = points[:,0]*width_mult + width_add
result[:,1] = points[:,1]*height_mult + height_add
return result.astype('int')
def on_screen_pixels(self, pixel_coords):
return reduce(op.and_, [
pixel_coords[:,0] >= 0,
pixel_coords[:,0] < self.pixel_width,
pixel_coords[:,1] >= 0,
pixel_coords[:,1] < self.pixel_height,
])
# def add_thickness(pixel_indices_and_rgbs, thickness, width, height):
# """
# Imagine dragging each pixel around like a paintbrush in
# a plus-sign-shaped pixel arrangement surrounding it.
# Pass rgb = None to do nothing to them
# """
# thickness = adjusted_thickness(thickness, width, height)
# original = np.array(pixel_indices_and_rgbs)
# n_extra_columns = pixel_indices_and_rgbs.shape[1] - 2
# for nudge in range(-thickness/2+1, thickness/2+1):
# if nudge == 0:
# continue
# for x, y in [[nudge, 0], [0, nudge]]:
# pixel_indices_and_rgbs = np.append(
# pixel_indices_and_rgbs,
# original+([x, y] + [0]*n_extra_columns),
# axis = 0
# )
# admissibles = (pixel_indices_and_rgbs[:,0] >= 0) & \
# (pixel_indices_and_rgbs[:,0] < width) & \
# (pixel_indices_and_rgbs[:,1] >= 0) & \
# (pixel_indices_and_rgbs[:,1] < height)
# return pixel_indices_and_rgbs[admissibles]
def adjusted_thickness(self, thickness):
big_width = PRODUCTION_QUALITY_DISPLAY_CONFIG["width"]
big_height = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"]
factor = (big_width + big_height) / \
(self.pixel_width + self.pixel_height)
return 1 + (thickness-1)/factor
def get_thickening_nudges(self, thickness):
_range = range(-thickness/2+1, thickness/2+1)
return np.array(list(it.product(*[_range]*2)))
def thickened_coordinates(self, pixel_coords, thickness):
nudges = self.get_thickening_nudges(thickness)
pixel_coords = np.array([
pixel_coords + nudge
for nudge in nudges
])
size = pixel_coords.size
return pixel_coords.reshape((size/2, 2))

View File

@ -84,6 +84,8 @@ MAX_LEN_FOR_HUGE_TEX_FONT = 25
LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png") LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png")
FFMPEG_BIN = "ffmpeg"
### Colors ### ### Colors ###

View File

@ -1,6 +1,5 @@
import numpy as np import numpy as np
import itertools as it import itertools as it
import subprocess as sp
import os import os
import sys import sys
from PIL import Image from PIL import Image
@ -10,165 +9,161 @@ import progressbar
from helpers import * from helpers import *
FFMPEG_BIN = "ffmpeg" class Camera(object):
DEFAULT_CONFIG = {
"pixel_width" : DEFAULT_WIDTH,
"pixel_height" : DEFAULT_HEIGHT,
"space_height" : SPACE_HEIGHT,
"space_center" : ORIGIN,
"background_color" : BLACK,
}
def get_pixels(image_array): #TODO, FIX WIDTH/HEIGHT PROBLEM HERE def __init__(self, background = None, **kwargs):
if image_array is None: digest_config(self, kwargs, locals())
return np.zeros( self.init_background()
(DEFAULT_HEIGHT, DEFAULT_WIDTH, 3), self.reset()
dtype = 'uint8'
)
else:
pixels = np.array(image_array).astype('uint8')
assert len(pixels.shape) == 3 and pixels.shape[2] == 3
return pixels
def paint_region(region, image_array = None, color = None): width_to_height = float(self.pixel_width) / self.pixel_height
pixels = get_pixels(image_array) self.space_width = self.space_height * width_to_height
assert region.shape == pixels.shape[:2]
if color is None:
#Random dark color
rgb = 0.5 * np.random.random(3)
else:
rgb = np.array(Color(color).get_rgb())
pixels[region.bool_grid] = (255*rgb).astype('uint8')
return pixels
def paint_mobject(mobject, image_array = None): def init_background(self):
return paint_mobjects([mobject], image_array) if self.background:
shape = self.background.shape[:2]
def paint_mobjects(mobjects, image_array = None, include_sub_mobjects = True): self.pixel_height, self.pixel_width = shape
pixels = get_pixels(image_array) else:
height = pixels.shape[0] background_color = Color(self.background_color)
width = pixels.shape[1] background_rgb = np.array(background_color.get_rgb())
space_height = SPACE_HEIGHT ones = np.ones(
space_width = SPACE_HEIGHT * width / height (self.pixel_height, self.pixel_width, 1),
pixels = pixels.reshape((pixels.size/3, 3)).astype('uint8') dtype = 'uint8'
)
if include_sub_mobjects: self.background = np.dot(
all_families = [ ones, background_rgb.reshape((1, 3))
mob.submobject_family()
for mob in mobjects
]
mobjects = reduce(op.add, all_families, [])
for mobject in mobjects:
if mobject.get_num_points() == 0:
continue
#bunch these together so rgbs never get lost from points
points_and_rgbs = np.append(
mobject.points,
255*mobject.rgbs,
axis = 1
)
points_and_rgbs = place_on_screen(
points_and_rgbs,
space_width, space_height
)
#Map points to pixel space, which requires rescaling and shifting
#Remember, 2*space_height -> height
points_and_rgbs[:,0] = points_and_rgbs[:,0]*width/space_width/2 + width/2
#Flip on y-axis as you go
points_and_rgbs[:,1] = -1*points_and_rgbs[:,1]*height/space_height/2 + height/2
points_and_rgbs = add_thickness(
points_and_rgbs.astype('int'),
mobject.point_thickness,
width, height
)
points, rgbs = points_and_rgbs[:,:2], points_and_rgbs[:,2:]
flattener = np.array([[1], [width]], dtype = 'int')
indices = np.dot(points, flattener)[:,0]
pixels[indices] = rgbs.astype('uint8')
return pixels.reshape((height, width, 3))
def add_thickness(pixel_indices_and_rgbs, thickness, width, height):
"""
Imagine dragging each pixel around like a paintbrush in
a plus-sign-shaped pixel arrangement surrounding it.
Pass rgb = None to do nothing to them
"""
thickness = adjusted_thickness(thickness, width, height)
original = np.array(pixel_indices_and_rgbs)
n_extra_columns = pixel_indices_and_rgbs.shape[1] - 2
for nudge in range(-thickness/2+1, thickness/2+1):
if nudge == 0:
continue
for x, y in [[nudge, 0], [0, nudge]]:
pixel_indices_and_rgbs = np.append(
pixel_indices_and_rgbs,
original+([x, y] + [0]*n_extra_columns),
axis = 0
) )
admissibles = (pixel_indices_and_rgbs[:,0] >= 0) & \
(pixel_indices_and_rgbs[:,0] < width) & \
(pixel_indices_and_rgbs[:,1] >= 0) & \
(pixel_indices_and_rgbs[:,1] < height)
return pixel_indices_and_rgbs[admissibles]
def adjusted_thickness(thickness, width, height): def get_image(self):
big_width = PRODUCTION_QUALITY_DISPLAY_CONFIG["width"] return self.pixel_array
big_height = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"]
factor = (big_width + big_height) / (width + height)
return 1 + (thickness-1)/factor
def place_on_screen(points_and_rgbs, space_width, space_height): def reset(self):
""" self.pixel_array = np.array(self.background)
Projects points to 2d space and remove those outside a
the space constraints.
Pass rbgs = None to do nothing to them. # def get_pixels(image_array): #TODO, FIX WIDTH/HEIGHT PROBLEM HERE
""" # if image_array is None:
# Remove 3rd column # return np.zeros(
points_and_rgbs = np.append( # (DEFAULT_HEIGHT, DEFAULT_WIDTH, 3),
points_and_rgbs[:, :2], # dtype = 'uint8'
points_and_rgbs[:, 3:], # )
axis = 1 # else:
) # pixels = np.array(image_array).astype('uint8')
# assert len(pixels.shape) == 3 and pixels.shape[2] == 3
#Removes points out of space # return pixels
to_keep = (abs(points_and_rgbs[:,0]) < space_width) & \
(abs(points_and_rgbs[:,1]) < space_height)
return points_and_rgbs[to_keep]
def get_file_path(name, extension): # def paint_region(region, image_array = None, color = None):
file_path = os.path.join(MOVIE_DIR, name) # pixels = get_pixels(image_array)
if not file_path.endswith(extension): # assert region.shape == pixels.shape[:2]
file_path += extension # if color is None:
directory = os.path.split(file_path)[0] # #Random dark color
if not os.path.exists(directory): # rgb = 0.5 * np.random.random(3)
os.makedirs(directory) # else:
return file_path # rgb = np.array(Color(color).get_rgb())
# pixels[region.bool_grid] = (255*rgb).astype('uint8')
# return pixels
def write_to_movie(scene, name): def capture_mobject(self, mobject):
file_path = get_file_path(name, ".mp4") return self.capture_mobjects([mobject])
print "Writing to %s"%file_path
fps = int(1/scene.frame_duration) def capture_mobjects(self, mobjects, include_sub_mobjects = True):
dim = (scene.width, scene.height) # pixels = get_pixels(image_array)
# height = pixels.shape[0]
# width = pixels.shape[1]
# space_height = SPACE_HEIGHT
# space_width = SPACE_HEIGHT * width / height
# pixels = pixels.reshape((pixels.size/3, 3)).astype('uint8')
command = [ if include_sub_mobjects:
FFMPEG_BIN, all_families = [
'-y', # overwrite output file if it exists mob.submobject_family()
'-f', 'rawvideo', for mob in mobjects
'-vcodec','rawvideo', ]
'-s', '%dx%d'%dim, # size of one frame mobjects = reduce(op.add, all_families, [])
'-pix_fmt', 'rgb24',
'-r', str(fps), # frames per second for mobject in mobjects:
'-i', '-', # The imput comes from a pipe self.display_points(
'-an', # Tells FFMPEG not to expect any audio mobject.points, mobject.rgbs,
'-vcodec', 'mpeg', mobject.point_thickness
'-c:v', 'libx264', )
'-pix_fmt', 'yuv420p',
'-loglevel', 'error', def display_points(self, points, rgbs, thickness):
file_path, points = self.project_onto_screen(points)
] pixel_coordinates = self.pixel_coordinates_of_points(points)
process = sp.Popen(command, stdin=sp.PIPE) rgbs = (255*rgbs).astype('uint8')
for frame in scene.frames: on_screen_indices = self.on_screen_pixels(pixel_coordinates)
process.stdin.write(frame.tostring()) pixel_coordinates = pixel_coordinates[on_screen_indices]
process.stdin.close() rgbs = rgbs[on_screen_indices]
process.wait()
flattener = np.array([1, self.width], dtype = 'int')
flattener = flattener.reshape((2, 1))
indices = np.dot(pixel_coordinates, flattener)[:,0]
pw, ph = self.pixel_width, self.pixel_height
self.pixel_array.reshape((pw*ph, 3))
self.pixel_array[indices] = rgbs.astype('uint8')
self.pixel_array.reshape((ph, pw, 3))
def project_onto_screen(points):
## TODO
points[:,2] = 0
def pixel_coordinates_of_points(self, points):
result = np.zeros((len(points), 2))
width_mult = self.pixel_width/self.space_width/2
width_add = self.pixel_width/2
height_mult = self.pixel_height/self.space_height/2
height_add = self.pixel_height/2
#Flip on y-axis as you go
height_mult *= -1
result[:,0] = points[:,0]*width_mult + width_add
result[:,1] = points[:,1]*height_mult + height_add
return result
def on_screen_pixels(self, pixel_coordinates):
return (pixel_coordinates[:,0] < 0) | \
(pixel_coordinates[:,0] >= width) | \
(pixel_coordinates[:,1] < 0) | \
(pixel_coordinates[:,1] >= height)
# def add_thickness(pixel_indices_and_rgbs, thickness, width, height):
# """
# Imagine dragging each pixel around like a paintbrush in
# a plus-sign-shaped pixel arrangement surrounding it.
# Pass rgb = None to do nothing to them
# """
# thickness = adjusted_thickness(thickness, width, height)
# original = np.array(pixel_indices_and_rgbs)
# n_extra_columns = pixel_indices_and_rgbs.shape[1] - 2
# for nudge in range(-thickness/2+1, thickness/2+1):
# if nudge == 0:
# continue
# for x, y in [[nudge, 0], [0, nudge]]:
# pixel_indices_and_rgbs = np.append(
# pixel_indices_and_rgbs,
# original+([x, y] + [0]*n_extra_columns),
# axis = 0
# )
# admissibles = (pixel_indices_and_rgbs[:,0] >= 0) & \
# (pixel_indices_and_rgbs[:,0] < width) & \
# (pixel_indices_and_rgbs[:,1] >= 0) & \
# (pixel_indices_and_rgbs[:,1] < height)
# return pixel_indices_and_rgbs[admissibles]
def adjusted_thickness(thickness, width, height):
big_width = PRODUCTION_QUALITY_DISPLAY_CONFIG["width"]
big_height = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"]
factor = (big_width + big_height) / (width + height)
return 1 + (thickness-1)/facto

View File

@ -8,13 +8,13 @@ import os
import copy import copy
from tqdm import tqdm as ProgressDisplay from tqdm import tqdm as ProgressDisplay
import inspect import inspect
import subprocess as sp
from helpers import * from helpers import *
import displayer as disp from camera import Camera
from tk_scene import TkSceneRoot from tk_scene import TkSceneRoot
from mobject import Mobject from mobject import Mobject
from animation.transform import ApplyMethod
class Scene(object): class Scene(object):
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
@ -27,16 +27,11 @@ class Scene(object):
} }
def __init__(self, **kwargs): def __init__(self, **kwargs):
digest_config(self, kwargs) digest_config(self, kwargs)
if self.background is not None: self.camera = Camera(
self.original_background = np.array(self.background) pixel_width = self.width,
#TODO, Error checking? pixel_height = self.height,
else: background = self.background
self.original_background = np.zeros( )
(self.height, self.width, 3),
dtype = 'uint8'
)
self.background = self.original_background
self.curr_frame = self.background
self.frames = [] self.frames = []
self.mobjects = [] self.mobjects = []
self.num_animations = 0 self.num_animations = 0
@ -57,11 +52,7 @@ class Scene(object):
return self return self
def get_frame(self): def get_frame(self):
return self.curr_frame return self.camera.get_image()
def set_frame(self, frame):
self.curr_frame = frame
return self
def add(self, *mobjects): def add(self, *mobjects):
""" """
@ -70,9 +61,9 @@ class Scene(object):
""" """
if not all_elements_are_instances(mobjects, Mobject): if not all_elements_are_instances(mobjects, Mobject):
raise Exception("Adding something which is not a mobject") raise Exception("Adding something which is not a mobject")
self.set_frame(disp.paint_mobjects(mobjects, self.get_frame()))
old_mobjects = filter(lambda m : m not in mobjects, self.mobjects) old_mobjects = filter(lambda m : m not in mobjects, self.mobjects)
self.mobjects = old_mobjects + list(mobjects) self.mobjects = old_mobjects + list(mobjects)
self.camera.capture_mobjects(mobjects)
return self return self
def add_mobjects_among(self, values): def add_mobjects_among(self, values):
@ -101,56 +92,17 @@ class Scene(object):
def bring_to_back(self, mobject): def bring_to_back(self, mobject):
self.remove(mobject) self.remove(mobject)
self.mobjects = [mobject] + self.mobjects self.mobjects = [mobject] + self.mobjects
self.repaint_mojects()
return self return self
def clear(self): def clear(self):
self.reset_background()
self.mobjects = [] self.mobjects = []
self.set_frame(self.background)
return self
def highlight_region(self, region, color = None):
self.background = disp.paint_region(
region,
image_array = self.background,
color = color,
)
self.repaint_mojects()
return self
def highlight_region_over_time_range(self, region, time_range = None, color = "black"):
if time_range:
frame_range = map(lambda t : t / self.frame_duration, time_range)
frame_range[0] = max(frame_range[0], 0)
frame_range[1] = min(frame_range[1], len(self.frames))
else:
frame_range = (0, len(self.frames))
for index in range(frame_range[0], frame_range[1]):
self.frames[index] = disp.paint_region(
region,
image_array = self.frames[index],
color = color
)
def reset_background(self):
self.background = self.original_background
return self return self
def repaint_mojects(self): def repaint_mojects(self):
self.set_frame(disp.paint_mobjects(self.mobjects, self.background)) self.camera.reset()
self.camera.capture_mobjects(self.mobjects)
return self return self
def paint_into_background(self, *mobjects):
#This way current mobjects don't have to be redrawn with
#every change, and one can later call "apply" without worrying
#about it applying to these mobjects
self.background = disp.paint_mobjects(mobjects, self.background)
return self
def set_frame_as_background(self):
self.background = self.get_frame()
def play(self, *animations, **kwargs): def play(self, *animations, **kwargs):
self.num_animations += 1 self.num_animations += 1
if "run_time" in kwargs: if "run_time" in kwargs:
@ -170,11 +122,12 @@ class Scene(object):
lambda m : m not in moving_mobjects, lambda m : m not in moving_mobjects,
bundle.submobject_family() bundle.submobject_family()
) )
background = disp.paint_mobjects( self.camera.reset()
self.camera.capture_mobjects(
static_mobjects, static_mobjects,
self.background,
include_sub_mobjects = False include_sub_mobjects = False
) )
static_image = self.camera.get_image()
times = np.arange(0, run_time, self.frame_duration) times = np.arange(0, run_time, self.frame_duration)
time_progression = ProgressDisplay(times) time_progression = ProgressDisplay(times)
@ -186,15 +139,15 @@ class Scene(object):
for t in time_progression: for t in time_progression:
for animation in animations: for animation in animations:
animation.update(t / animation.run_time) animation.update(t / animation.run_time)
new_frame = disp.paint_mobjects( self.camera.capture_mobjects([
[anim.mobject for anim in animations], anim.mobject
background for anim in animations
) ])
self.frames.append(new_frame) self.frames.append(self.camera.get_image())
self.camera.set_image(static_image)
for animation in animations: for animation in animations:
animation.clean_up() animation.clean_up()
self.add(*[anim.mobject for anim in animations]) self.add(*[anim.mobject for anim in animations])
self.repaint_mojects()
return self return self
def play_over_time_range(self, t0, t1, *animations): def play_over_time_range(self, t0, t1, *animations):
@ -215,14 +168,14 @@ class Scene(object):
for animation in animations: for animation in animations:
animation.update((t-t0)/(t1 - t0)) animation.update((t-t0)/(t1 - t0))
index = int(t/self.frame_duration) index = int(t/self.frame_duration)
self.frames[index] = disp.paint_mobjects(moving_mobjects, self.frames[index]) self.camera.set_image(self.frames[index])
self.camera.capture_mobjects(moving_mobjects)
self.frames[index] = self.camera.get_image()
for animation in animations: for animation in animations:
animation.clean_up() animation.clean_up()
self.repaint_mojects()
return self return self
def apply(self, mobject_method, *args, **kwargs):
self.play(ApplyMethod(mobject_method, *args, **kwargs))
def dither(self, duration = DEFAULT_DITHER_TIME): def dither(self, duration = DEFAULT_DITHER_TIME):
self.frames += [self.get_frame()]*int(duration / self.frame_duration) self.frames += [self.get_frame()]*int(duration / self.frame_duration)
return self return self
@ -243,14 +196,6 @@ class Scene(object):
] ]
return self return self
def write_to_movie(self, name = None):
if len(self.frames) == 0:
print "No frames, I'll just save an image instead"
self.show_frame()
self.save_image(name = name)
return
disp.write_to_movie(self, name or str(self))
def show_frame(self): def show_frame(self):
Image.fromarray(self.get_frame()).show() Image.fromarray(self.get_frame()).show()
@ -265,6 +210,52 @@ class Scene(object):
os.makedirs(path) os.makedirs(path)
Image.fromarray(self.get_frame()).save(full_path) Image.fromarray(self.get_frame()).save(full_path)
def get_movie_file_path(self, name, extension):
file_path = os.path.join(MOVIE_DIR, name)
if not file_path.endswith(extension):
file_path += extension
directory = os.path.split(file_path)[0]
if not os.path.exists(directory):
os.makedirs(directory)
return file_path
def write_to_movie(self, name = None):
if len(self.frames) == 0:
print "No frames, I'll just save an image instead"
self.show_frame()
self.save_image(name = name)
return
if name is None:
name = str(self)
file_path = self.get_movie_file_path(name, ".mp4")
print "Writing to %s"%file_path
fps = int(1/self.frame_duration)
dim = (self.width, self.height)
command = [
FFMPEG_BIN,
'-y', # overwrite output file if it exists
'-f', 'rawvideo',
'-vcodec','rawvideo',
'-s', '%dx%d'%dim, # size of one frame
'-pix_fmt', 'rgb24',
'-r', str(fps), # frames per second
'-i', '-', # The imput comes from a pipe
'-an', # Tells FFMPEG not to expect any audio
'-vcodec', 'mpeg',
'-c:v', 'libx264',
'-pix_fmt', 'yuv420p',
'-loglevel', 'error',
file_path,
]
process = sp.Popen(command, stdin=sp.PIPE)
for frame in self.frames:
process.stdin.write(frame.tostring())
process.stdin.close()
process.wait()
# To list possible args that subclasses have # To list possible args that subclasses have
# Elements should always be a tuple # Elements should always be a tuple
args_list = [] args_list = []

View File

@ -27,8 +27,6 @@ class PiCreature(Mobject):
'mouth', 'mouth',
] ]
WHITE_PART_NAMES = ['left_eye', 'right_eye', 'mouth'] WHITE_PART_NAMES = ['left_eye', 'right_eye', 'mouth']
MOUTH_NAMES = ["smile", "frown", "straight_mouth"]
def __init__(self, **kwargs): def __init__(self, **kwargs):
Mobject.__init__(self, **kwargs) Mobject.__init__(self, **kwargs)
for part_name in self.PART_NAMES: for part_name in self.PART_NAMES:
@ -39,43 +37,26 @@ class PiCreature(Mobject):
if part_name not in self.WHITE_PART_NAMES: if part_name not in self.WHITE_PART_NAMES:
mob.highlight(self.color) mob.highlight(self.color)
setattr(self, part_name, mob) setattr(self, part_name, mob)
self.add(mob)
self.eyes = Mobject(self.left_eye, self.right_eye) self.eyes = Mobject(self.left_eye, self.right_eye)
self.legs = Mobject(self.left_leg, self.right_leg) self.legs = Mobject(self.left_leg, self.right_leg)
mouth_center = self.get_mouth_center() self.mouth.center().shift(self.get_mouth_center())
self.mouth.center()
self.smile = self.mouth
self.frown = self.mouth.copy().rotate(np.pi, RIGHT)
self.straight_mouth = TexMobject("-").scale(0.7)
for mouth in self.smile, self.frown, self.straight_mouth:
mouth.sort_points(lambda p : p[0])
mouth.highlight(self.color) ##to blend into background
mouth.shift(mouth_center)
self.digest_mobject_attrs()
self.give_smile()
self.add(self.mouth) self.add(self.mouth)
self.scale(PI_CREATURE_SCALE_VAL) self.scale(PI_CREATURE_SCALE_VAL)
def get_parts(self): def get_parts(self):
return [getattr(self, pn) for pn in self.PART_NAMES] return [getattr(self, pn) for pn in self.PART_NAMES]
def get_white_parts(self): def get_white_parts(self):
return [ return [
getattr(self, pn) getattr(self, pn)
for pn in self.WHITE_PART_NAMES+self.MOUTH_NAMES for pn in self.WHITE_PART_NAMES
] ]
def get_mouth_center(self): def get_mouth_center(self):
result = self.body.get_center() result = self.body.get_center()
result[0] = self.eyes.get_center()[0] result[0] = self.eyes.get_center()[0]
return result return result
# left_center = self.left_eye.get_center()
# right_center = self.right_eye.get_center()
# l_to_r = right_center-left_center
# eyes_to_mouth = rotate_vector(l_to_r, -np.pi/2, OUT)
# eyes_to_mouth /= np.linalg.norm(eyes_to_mouth)
# return left_center/2 + right_center/2 + \
# PI_CREATURE_MOUTH_TO_EYES_DISTANCE*eyes_to_mouth
def highlight(self, color, condition = None): def highlight(self, color, condition = None):
for part in set(self.get_parts()).difference(self.get_white_parts()): for part in set(self.get_parts()).difference(self.get_white_parts()):
@ -86,25 +67,6 @@ class PiCreature(Mobject):
self.shift(destination-self.get_bottom()) self.shift(destination-self.get_bottom())
return self return self
def change_mouth_to(self, mouth_name):
#TODO, This is poorly implemented
self.mouth = getattr(self, mouth_name)
self.sub_mobjects = list_update(
self.sub_mobjects,
self.get_parts()
)
self.mouth.highlight(WHITE)
return self
def give_smile(self):
return self.change_mouth_to("smile")
def give_frown(self):
return self.change_mouth_to("frown")
def give_straight_face(self):
return self.change_mouth_to("straight_mouth")
def get_eye_center(self): def get_eye_center(self):
return self.eyes.get_center() return self.eyes.get_center()