Proper script wrapping implemented

This commit is contained in:
Grant Sanderson
2015-05-07 21:28:02 -07:00
parent ed7061e7cf
commit ba7e4a79cf
9 changed files with 855 additions and 861 deletions

View File

@ -1,15 +1,29 @@
import os
PRODUCTION_QUALITY = True
GENERALLY_BUFF_POINTS = PRODUCTION_QUALITY
DEFAULT_POINT_DENSITY_2D = 25 #if PRODUCTION_QUALITY else 20
DEFAULT_POINT_DENSITY_1D = 150 #if PRODUCTION_QUALITY else 50
GENERALLY_BUFF_POINTS = True
DEFAULT_HEIGHT = 1440 if PRODUCTION_QUALITY else 480
DEFAULT_WIDTH = 2560 if PRODUCTION_QUALITY else 840
PRODUCTION_QUALITY_DISPLAY_CONFIG = {
"height" : 1440,
"width" : 2560,
"frame_duration" : 0.04,
}
LOW_QUALITY_DISPLAY_CONFIG = {
"height" : 480,
"width" : 840,
"frame_duration" : 0.15,
}
DEFAULT_POINT_DENSITY_2D = 25
DEFAULT_POINT_DENSITY_1D = 150
#TODO, Make sure these are not needd
DEFAULT_HEIGHT = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"]
DEFAULT_WIDTH = PRODUCTION_QUALITY_DISPLAY_CONFIG["width"]
#All in seconds
DEFAULT_FRAME_DURATION = 0.04 if PRODUCTION_QUALITY else 0.1
DEFAULT_FRAME_DURATION = 0.04
DEFAULT_ANIMATION_RUN_TIME = 3.0
DEFAULT_TRANSFORM_RUN_TIME = 1.0
DEFAULT_DITHER_TIME = 1.0
@ -18,7 +32,7 @@ DEFAULT_DITHER_TIME = 1.0
DEFAULT_NUM_STARS = 1000
SPACE_HEIGHT = 4.0
SPACE_WIDTH = DEFAULT_WIDTH * SPACE_HEIGHT / DEFAULT_HEIGHT
SPACE_WIDTH = DEFAULT_WIDTH * DEFAULT_HEIGHT / DEFAULT_WIDTH
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
IMAGE_DIR = os.path.join(THIS_DIR, "images")

View File

@ -80,7 +80,7 @@ def write_to_gif(scene, name):
temppath = os.path.join(GIF_DIR, "Temp.gif")
print "Writing " + name + "..."
images = [Image.fromarray(frame) for frame in scene.frames]
writeGif(temppath, images, scene.frame_duration)
writeGif(temppath, images, scene.display_config["frame_duration"])
print "Compressing..."
os.system("gifsicle -O " + temppath + " > " + filepath)
os.system("rm " + temppath)
@ -95,7 +95,7 @@ def write_to_movie(scene, name):
filedir = "/".join(filepath.split("/")[:-1])
if not os.path.exists(filedir):
os.makedirs(filedir)
rate = int(1/scene.frame_duration)
rate = int(1/scene.display_config["frame_duration"])
tmp_stem = os.path.join(TMP_IMAGE_DIR, name.replace("/", "_"))
suffix = "-%04d.png"
@ -114,7 +114,7 @@ def write_to_movie(scene, name):
"-c:v",
"libx264",
"-vf",
"fps=%d,format=yuv420p"%int(1/scene.frame_duration),
"fps=%d,format=yuv420p"%rate,
filepath + ".mp4"
]
os.system(" ".join(commands))
@ -148,35 +148,35 @@ def write_to_movie(scene, name):
# progress_bar.finish()
class VideoSink(object):
def __init__(self, size, filename="output", rate=10, byteorder="bgra") :
self.size = size
cmdstring = [
'mencoder',
'/dev/stdin',
'-demuxer', 'rawvideo',
'-rawvideo', 'w=%i:h=%i'%size[::-1]+":fps=%i:format=%s"%(rate,byteorder),
'-o', filename+'.mp4',
'-ovc', 'lavc',
]
self.p = subprocess.Popen(cmdstring, stdin=subprocess.PIPE, shell=False)
# class VideoSink(object):
# def __init__(self, size, filename="output", rate=10, byteorder="bgra") :
# self.size = size
# cmdstring = [
# 'mencoder',
# '/dev/stdin',
# '-demuxer', 'rawvideo',
# '-rawvideo', 'w=%i:h=%i'%size[::-1]+":fps=%i:format=%s"%(rate,byteorder),
# '-o', filename+'.mp4',
# '-ovc', 'lavc',
# ]
# self.p = subprocess.Popen(cmdstring, stdin=subprocess.PIPE, shell=False)
def run(self, image):
"""
Image comes in as HEIGHTxWIDTHx3 numpy array, order rgb
"""
assert image.shape == self.size + (3,)
r, g, b = [image[:,:,i].astype('uint32') for i in range(3)]
a = np.ones(image.shape[:2], dtype = 'uint32')
#hacky
image = sum([
arr << 8**i
for arr, i in zip(range(4), [a, r, g, b])
])
self.p.stdin.write(image.tostring())
# def run(self, image):
# """
# Image comes in as HEIGHTxWIDTHx3 numpy array, order rgb
# """
# assert image.shape == self.size + (3,)
# r, g, b = [image[:,:,i].astype('uint32') for i in range(3)]
# a = np.ones(image.shape[:2], dtype = 'uint32')
# #hacky
# image = sum([
# arr << 8**i
# for arr, i in zip(range(4), [a, r, g, b])
# ])
# self.p.stdin.write(image.tostring())
def close(self):
self.p.stdin.close()
# def close(self):
# self.p.stdin.close()

View File

@ -28,6 +28,9 @@ def initials(name, sep_values = [" ", "_"]):
s[0] for s in re.split("|".join(sep_values), name)
])
def cammel_case_initials(name):
return filter(lambda c : c.isupper(), name)
################################################
def drag_pixels(frames):

View File

@ -99,9 +99,13 @@ def tex_mobjects(expression, size = r"\HUGE"):
images = tex_to_image(expression, size)
if isinstance(images, list):
#TODO, is checking listiness really the best here?
return [ImageMobject(im).scale(2) for im in images]
result = [ImageMobject(im).scale(2) for im in images]
center = CompoundMobject(*result).get_center()
for mob in result:
mob.shift(-center)
return result
else:
return ImageMobject(images).scale(2)
return ImageMobject(images).center().scale(2)

View File

@ -264,7 +264,7 @@ class Arrow(Mobject1D):
[x, x, x] * self.direction + self.point
for x in np.arange(-self.length, 0, self.epsilon)
])
tips_dir = np.array(-self.direction), np.array(-self.direction)
tips_dir = [np.array(-self.direction), np.array(-self.direction)]
for i, sgn in zip([0, 1], [-1, 1]):
tips_dir[i] = rotate_vector(tips_dir[i], sgn * np.pi / 4, self.normal)
self.add_points([

File diff suppressed because it is too large Load Diff

View File

@ -7,12 +7,26 @@ from image_mobject import *
from region import *
from scene import Scene
RADIUS = SPACE_HEIGHT - 0.1
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS
from graphs import *
RADIUS = SPACE_HEIGHT - 0.1
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS
MOVIE_PREFIX = "moser/"
RADIANS = np.arange(0, 6, 6.0/7)
N_PASCAL_ROWS = 7
BIG_N_PASCAL_ROWS = 11
############################################
class CircleScene(Scene):
args_list = [
(RADIANS[:x],)
for x in range(1, len(RADIANS)+1)
]
@staticmethod
def args_to_string(*args):
return str(len(args[0])) #Length of radians
def __init__(self, radians, *args, **kwargs):
Scene.__init__(self, *args, **kwargs)
self.radius = RADIUS
@ -29,6 +43,7 @@ class CircleScene(Scene):
).shift((-SPACE_WIDTH+1, SPACE_HEIGHT-1.5, 0))
self.add(self.circle, self.n_equals, *self.dots + self.lines)
def generate_intersection_dots(self):
"""
Generates and adds attributes intersection_points and
@ -95,8 +110,15 @@ class CircleScene(Scene):
self.add(*self.circle_pieces)
class GraphScene(Scene):
#Note, the placement of vertices in this is pretty hard coded, be
#warned if you want to change it.
args_list = [
(CUBE_GRAPH,),
(SAMPLE_GRAPH,),
(OCTOHEDRON_GRAPH,),
]
@staticmethod
def args_to_string(*args):
return args[0]["name"]
def __init__(self, graph, *args, **kwargs):
Scene.__init__(self, *args, **kwargs)
#See CUBE_GRAPH above for format of graph
@ -123,7 +145,15 @@ class GraphScene(Scene):
regions[-1].complement()#Outer region painted outwardly...
self.regions = regions
class PascalsTriangleScene(Scene):
class PascalsTriangleScene(Scene):
args_list = [
(N_PASCAL_ROWS,),
(BIG_N_PASCAL_ROWS,),
]
@staticmethod
def args_to_string(*args):
return str(args[0])
def __init__(self, nrows, *args, **kwargs):
Scene.__init__(self, *args, **kwargs)
diagram_height = 2*SPACE_HEIGHT - 1
@ -167,7 +197,6 @@ class PascalsTriangleScene(Scene):
self.portion_to_fill= portion_to_fill
self.coords_to_mobs = coords_to_mobs
def generate_n_choose_k_mobs(self):
self.coords_to_n_choose_k = {}
for n, k in self.coords:
@ -187,6 +216,9 @@ class PascalsTriangleScene(Scene):
##################################################
def int_list_to_string(int_list):
return "-".join(map(str, int_list))
def choose(n, r):
if n < r: return 0
if r == 0: return 1

View File

@ -20,14 +20,12 @@ DEFAULT_COUNT_NUM_OFFSET = (SPACE_WIDTH - 1, SPACE_HEIGHT - 1, 0)
DEFAULT_COUNT_RUN_TIME = 5.0
class Scene(object):
def __init__(self,
name = None,
frame_duration = DEFAULT_FRAME_DURATION,
def __init__(self,
display_config = PRODUCTION_QUALITY_DISPLAY_CONFIG,
background = None,
height = DEFAULT_HEIGHT,
width = DEFAULT_WIDTH,
start_dither_time = DEFAULT_DITHER_TIME):
self.frame_duration = frame_duration
self.display_config = display_config
self.frame_duration = display_config["frame_duration"]
self.frames = []
self.mobjects = []
if background:
@ -35,16 +33,15 @@ class Scene(object):
#TODO, Error checking?
else:
self.original_background = np.zeros(
(height, width, 3),
(display_config["height"], display_config["width"], 3),
dtype = 'uint8'
)
self.background = self.original_background
self.shape = self.background.shape[:2]
#TODO, space shape
self.name = name
def __str__(self):
return self.name or "Babadinook" #TODO
return self.__class__.__name__
def set_name(self, name):
self.name = name
@ -178,17 +175,28 @@ class Scene(object):
def dither(self, duration = DEFAULT_DITHER_TIME):
self.frames += [self.get_frame()]*int(duration / self.frame_duration)
def write_to_gif(self, name = None, end_dither_time = DEFAULT_DITHER_TIME):
def write_to_gif(self, name = None,
end_dither_time = DEFAULT_DITHER_TIME):
self.dither(end_dither_time)
disp.write_to_gif(self, name or str(self))
def write_to_movie(self, name = None, end_dither_time = DEFAULT_DITHER_TIME):
def write_to_movie(self, name = None,
end_dither_time = DEFAULT_DITHER_TIME):
self.dither(end_dither_time)
disp.write_to_movie(self, name or str(self))
def show(self):
Image.fromarray(self.get_frame()).show()
# To list possible args that subclasses have
# Elements should always be a tuple
args_list = []
# For subclasses to turn args in the above
# list into stings which can be appended to the name
@staticmethod
def args_to_string(*args):
return ""

View File

@ -2,72 +2,100 @@ import sys
import getopt
import imp
import itertools as it
from helpers import initials, to_cammel_case
from helpers import cammel_case_initials
def print_help_message():
print '<script name> -f <function name or initials>'
from constants import *
def get_scene_and_name(function_string, args_string,
function_tuples):
possible_func_args = []
for func, args_list, args_to_string in function_tuples:
if function_string in ("", func.__name__, initials(func.__name__)):
for args in args_list:
if not isinstance(args, tuple):
args = (args,)
if not args_to_string:
args_to_string = lambda x : ""
this_args_string = args_to_string(args)
if args_string in ("", this_args_string):
possible_func_args.append(
(func, args, this_args_string)
)
if len(possible_func_args) == 0:
print "No function_string arg_string pair \
matching \"%s\" and \"%s\""%(function_string, args_string)
HELP_MESSAGE = """
<script name> [-s <scene name or initials> -a <arg_string>]
"""
SCENE_NOT_FOUND_MESSAGE = """
No scene name or ititials \"%s\" and arg string \"%s\"
"""
MULTIPE_SCENES_FOUND_MESSAGE = """
Multiple Scene/arg pairs satisfying this
description, choose from the following list:
"""
CHOOSE_NUMBER_MESSAGE = "Choose a number from above: "
INVALID_NUMBER_MESSAGE = "Invalid number!"
def find_scene_class_and_args(scene_string, args_extension,
scene_classes):
possible_results = []
for SceneClass in scene_classes:
possible_names = map(str.lower, (
"",
SceneClass.__name__,
cammel_case_initials(SceneClass.__name__)
))
if scene_string.lower() in possible_names:
if len(SceneClass.args_list) == 0:
possible_results.append((SceneClass, ()))
for args in SceneClass.args_list:
assert(isinstance(args, tuple))
this_args_extension = SceneClass.args_to_string(*args)
if args_extension.lower() in ("", this_args_extension.lower()):
possible_results.append((SceneClass, args))
num_matches = len(possible_results)
if num_matches == 0:
print SCENE_NOT_FOUND_MESSAGE%(scene_string, args_extension)
sys.exit(0)
elif len(possible_func_args) == 1:
elif num_matches == 1:
index = 0
else:
print "Multiple functions/arg pairs satisfying this " + \
"description, choose from the following list:"
print MULTIPE_SCENES_FOUND_MESSAGE
count = 0
for func, args, args_string in possible_func_args:
print "%d: %s%s"%(
for SceneClass, args in possible_results:
print "%d: %s%s"%(
count,
to_cammel_case(func.__name__),
args_string
SceneClass.__name__,
SceneClass.args_to_string(*args),
)
count += 1
index = int(raw_input("Choose a number from above: "))
function, args, args_string = possible_func_args[index]
scene_name = to_cammel_case(function.__name__) + args_string
print "Writing %s..."%scene_name
scene = function(*args)
return scene, scene_name
try:
index = input(CHOOSE_NUMBER_MESSAGE)
assert(type(index) == int)
assert(index in range(num_matches))
except:
print INVALID_NUMBER_MESSAGE
sys.exit(0)
return possible_results[index]
def create_scene(sys_argv, function_tuples, movie_prefix = ""):
def command_line_create_scene(sys_argv, scene_classes, movie_prefix = ""):
try:
opts, args = getopt.getopt(sys_argv, "ho:vf:v")#TODO, Learn about this
opts, args = getopt.getopt(sys_argv, "ho:s:a:l")
except getopt.GetoptError as err:
print str(err)
sys.exit(2)
function_string = ""
scene_string = ""
args_extension = ""
display_config = PRODUCTION_QUALITY_DISPLAY_CONFIG
for opt, arg in opts:
if opt == '-h':
print_help_message()
print HELP_MESSAGE
return
elif opt == '-f':
function_string = arg
elif opt == "-a":
elif opt == '-s':
scene_string = arg
elif opt == '-a':
args_extension = arg
scene, name = get_scene_and_name(
function_string,
elif opt == '-l':
display_config = LOW_QUALITY_DISPLAY_CONFIG
SceneClass, args = find_scene_class_and_args(
scene_string,
args_extension,
function_tuples
scene_classes
)
print name
name = SceneClass.__name__ + SceneClass.args_to_string(*args)
print "Writing %s..."%name
scene = SceneClass(*args, display_config = display_config)
scene.write_to_movie(movie_prefix + name)