Changed the defaults for where animations are written, and where images for ImageMobject and SVGMobject are sought after

This commit is contained in:
Grant Sanderson 2018-01-12 13:38:25 -08:00
parent 0b595ed5f5
commit ffcd9b5767
19 changed files with 68 additions and 147 deletions

View file

@ -36,7 +36,7 @@ python extract_scene.py -p example_scenes.py SquareToCircle
`-p` gives a preview of an animation, `-w` will write it to a file, and `-s` will show/save the final image in the animation.
You will probably want to change the MOVIE_DIR constant to be whatever direction you want video files to output to.
You will probably want to change the ANIMATIONS_DIR constant to be whatever direction you want video files to output to.
Look through the old_projects folder to see the code for previous 3b1b videos.

View file

@ -49,7 +49,7 @@ class Camera(object):
def init_background(self):
if self.background_image is not None:
path = get_full_image_path(self.background_image)
path = get_full_raster_image_path(self.background_image)
image = Image.open(path).convert(self.image_mode)
height, width = self.pixel_shape
#TODO, how to gracefully handle backgrounds

View file

@ -61,19 +61,23 @@ RIGHT_SIDE = SPACE_WIDTH*RIGHT
# Change this to point to where you want
# animation files to output
MOVIE_DIR = os.path.join(os.path.expanduser('~'), "Dropbox (3Blue1Brown)/3Blue1Brown Team Folder/animations")
STAGED_SCENES_DIR = os.path.join(MOVIE_DIR, "staged_scenes")
MEDIA_DIR = os.path.join(os.path.expanduser('~'), "Dropbox (3Blue1Brown)/3Blue1Brown Team Folder")
ANIMATIONS_DIR = os.path.join(MEDIA_DIR, "animations")
RASTER_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "raster_images")
SVG_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "svg_images")
#TODO, staged scenes should really go into a subdirectory of a given scenes directory
STAGED_SCENES_DIR = os.path.join(ANIMATIONS_DIR, "staged_scenes")
###
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
FILE_DIR = os.path.join(THIS_DIR, "files")
IMAGE_DIR = os.path.join(FILE_DIR, "images")
GIF_DIR = os.path.join(FILE_DIR, "gifs")
TEX_DIR = os.path.join(FILE_DIR, "Tex")
TEX_IMAGE_DIR = os.path.join(IMAGE_DIR, "Tex")
TEX_IMAGE_DIR = TEX_DIR #TODO, What is this doing?
#These two may be depricated now.
MOBJECT_DIR = os.path.join(FILE_DIR, "mobjects")
IMAGE_MOBJECT_DIR = os.path.join(MOBJECT_DIR, "image")
for folder in [FILE_DIR, IMAGE_DIR, GIF_DIR, MOVIE_DIR, TEX_DIR,
for folder in [FILE_DIR, RASTER_IMAGE_DIR, SVG_IMAGE_DIR, ANIMATIONS_DIR, TEX_DIR,
TEX_IMAGE_DIR, MOBJECT_DIR, IMAGE_MOBJECT_DIR,
STAGED_SCENES_DIR]:
if not os.path.exists(folder):
@ -84,8 +88,6 @@ TEMPLATE_TEX_FILE = os.path.join(THIS_DIR, "template.tex")
TEMPLATE_TEXT_FILE = os.path.join(THIS_DIR, "text_template.tex")
LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png")
FFMPEG_BIN = "ffmpeg"

View file

@ -190,7 +190,7 @@ def main():
)
config["output_directory"] = os.path.join(
MOVIE_DIR,
ANIMATIONS_DIR,
config["file"].replace(".py", "")
)

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 480 480" style="enable-background:new 0 0 480 480;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;stroke:#000000;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-miterlimit:10;}
</style>
<path class="st1" d="M220.1,222.2l-93.9,73.6l127.4-70.5c3.4,0.1,6.8,0.2,10.2,0.2c90.3,0,163.5-40.5,163.5-90.4
s-73.2-90.4-163.5-90.4S100.3,85.1,100.3,135C100.3,176.6,151,211.6,220.1,222.2L220.1,222.2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 716 B

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 480 480" style="enable-background:new 0 0 480 480;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;stroke:#000000;stroke-miterlimit:10;}
.st1{fill:none;stroke:#000000;stroke-miterlimit:10;}
</style>
<circle class="st1" cx="143.8" cy="268" r="22.6"/>
<circle class="st1" cx="111.8" cy="313.2" r="13.2"/>
<circle class="st1" cx="80.7" cy="334.2" r="7"/>
<path class="st1" d="M293.8,49.9c3-1,6.4-1.9,9.7-1.9c16.8,0,30.4,13.6,30.4,30.4c0,1.9-0.2,3.7-0.5,5.5l0,0
c10.8,4.5,21,16.2,20.5,28.6c-0.4,9.1-1.2,18.8-11.3,27.7l0,0c8.5,6.8,8.7,29.6,4.7,39c-4.1,9.7-11.8,19.6-33.4,27.2
c-8.2,19.4-30.1,35.7-52.4,35.7c-9.6,0-18.9-2.5-26.7-6.9l0,0c-8.1,5.6-17.7,8.8-28.3,8.8c-18.9,0-35.8-10.6-44.4-26l0,0
c-22.5,0-45.3-15.3-49.5-36.6l0,0c-19.2-5.7-33.1-23.4-33.1-44.5c0-9.7,2.9-18.7,8-26.2l0,0c-2.7-5.5-4.2-11.8-4.2-18.3
c0-21.9,17.3-40,38.8-41.6l0,0c7.3-14.6,22.4-24.7,39.9-24.7c4.8,0,9.3,0.7,13.6,2.1l0,0c7.6-8.3,18.7-13.5,30.9-13.5
c12.8,0,24.2,5.7,31.9,14.8l0,0c6-4.7,13.6-7.6,21.7-7.6C277,22.1,290.5,33.9,293.8,49.9L293.8,49.9z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 480 480" style="enable-background:new 0 0 480 480;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#0C7F99;}
</style>
<path class="st0" d="M251.6,114.7c0,4.2-1.4,8.2-3.7,11.3l-30.4,0.3c-2.6-3.1-4-7.3-4-11.6c0-10.5,8.5-19,19-19
C243.1,95.6,251.6,104.2,251.6,114.7z"/>
<path class="st0" d="M306.6,114.7c0,4.2-1.4,8.2-3.7,11.3l-30.4,0.3c-2.6-3.1-4-7.3-4-11.6c0-10.5,8.5-19,19-19
C298.1,95.6,306.6,104.2,306.6,114.7z"/>
<path d="M238,99.8c-1.3,0.1-2.3,1.1-2.3,2.4c0,1.3,1.1,2.4,2.4,2.4c1.3,0,2.4-1.1,2.4-2.4c0-1.3-1.1-2.4-2.4-2.4
C238.1,99.8,238,99.8,238,99.8l2.9-1c4.3,0,7.7,3.5,7.7,7.7s-3.5,7.7-7.7,7.7s-7.7-3.5-7.7-7.7s3.5-7.7,7.7-7.7"/>
<path d="M293,99.8c-1.3,0.1-2.3,1.1-2.3,2.4c0,1.3,1.1,2.4,2.4,2.4s2.4-1.1,2.4-2.4c0-1.3-1.1-2.4-2.4-2.4
C293.1,99.8,293,99.8,293,99.8l2.9-1c4.3,0,7.7,3.5,7.7,7.7s-3.5,7.7-7.7,7.7s-7.7-3.5-7.7-7.7s3.5-7.7,7.7-7.7"/>
<path class="st1" d="M228.4,157.1h64.4c-15.4,63.8-25.1,105.4-25.1,151c0,8,0,69.5,23.4,69.5c12,0,22.2-10.8,22.2-20.5
c0-2.8,0-4-4-12.5c-15.4-39.3-15.4-88.3-15.4-92.3c0-3.4,0-43.9,12-95.1h63.8c7.4,0,26.2,0,26.2-18.2c0-12.5-10.8-12.5-21.1-12.5
H187.4c-13.1,0-32.5,0-58.7,27.9c-14.8,16.5-33,46.7-33,50.1s2.8,4.6,6.3,4.6c4,0,4.6-1.7,7.4-5.1c29.6-46.7,59.2-46.7,73.5-46.7
h32.5c-12.5,42.7-26.8,92.3-73.5,192c-4.6,9.1-4.6,10.3-4.6,13.7c0,12,10.3,14.8,15.4,14.8c16.5,0,21.1-14.8,27.9-38.7
c9.1-29.1,9.1-30.2,14.8-53L228.4,157.1"/>
<path d="M246.3,149.1c5.5,0,1.9,0.1,13.5,0c10.3-0.1,12.4-0.1,15.7-0.2c0,0-0.3-3.1-0.3-3.1c-3.7,0.1-9.3,0.2-14.7,0.2
c-3.9,0-8.5,0-14.6,0"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -410,13 +410,13 @@ def cammel_case_initials(name):
################################################
def get_full_image_path(image_file_name):
def get_full_raster_image_path(image_file_name):
possible_paths = [
image_file_name,
os.path.join(IMAGE_DIR, image_file_name),
os.path.join(IMAGE_DIR, image_file_name + ".jpg"),
os.path.join(IMAGE_DIR, image_file_name + ".png"),
os.path.join(IMAGE_DIR, image_file_name + ".gif"),
os.path.join(RASTER_IMAGE_DIR, image_file_name),
os.path.join(RASTER_IMAGE_DIR, image_file_name + ".jpg"),
os.path.join(RASTER_IMAGE_DIR, image_file_name + ".png"),
os.path.join(RASTER_IMAGE_DIR, image_file_name + ".gif"),
]
for path in possible_paths:
if os.path.exists(path):

View file

@ -23,7 +23,7 @@ class ImageMobject(Mobject):
def __init__(self, filename_or_array, **kwargs):
digest_config(self, kwargs)
if isinstance(filename_or_array, str):
path = get_full_image_path(filename_or_array)
path = get_full_raster_image_path(filename_or_array)
image = Image.open(path).convert(self.image_mode)
self.pixel_array = np.array(image)
else:

View file

@ -96,7 +96,7 @@ class Mobject(object):
def save_image(self, name = None):
self.get_image().save(
os.path.join(MOVIE_DIR, (name or str(self)) + ".png")
os.path.join(ANIMATIONS_DIR, (name or str(self)) + ".png")
)
def copy(self):

View file

@ -37,8 +37,8 @@ class SVGMobject(VMobject):
raise Exception("Must specify file for SVGMobject")
possible_paths = [
self.file_name,
os.path.join(IMAGE_DIR, self.file_name),
os.path.join(IMAGE_DIR, self.file_name + ".svg"),
os.path.join(SVG_IMAGE_DIR, self.file_name),
os.path.join(SVG_IMAGE_DIR, self.file_name + ".svg"),
]
for path in possible_paths:
if os.path.exists(path):

View file

@ -1532,7 +1532,7 @@ class BreakUpMacroPatterns(IntroduceEachLayer):
prefixes = self.prefixes
vects = [
np.array(Image.open(
get_full_image_path("handwritten_" + p),
get_full_raster_image_path("handwritten_" + p),
))[:,:,0].flatten()/255.0
for p in prefixes
]
@ -1921,7 +1921,7 @@ class SecondLayerIsLittleEdgeLayer(IntroduceEachLayer):
def setup_activations_and_nines(self):
layers = self.network_mob.layers
nine_im, loop_im, line_im = images = [
Image.open(get_full_image_path("handwritten_%s"%s))
Image.open(get_full_raster_image_path("handwritten_%s"%s))
for s in "nine", "upper_loop", "right_line"
]
nine_array, loop_array, line_array = [

View file

@ -14,7 +14,7 @@ from scene import Scene, SceneFromVideo
from script_wrapper import command_line_create_scene
MOVIE_PREFIX = "counting_in_binary/"
BASE_HAND_FILE = os.path.join(MOVIE_DIR, MOVIE_PREFIX, "Base.mp4")
BASE_HAND_FILE = os.path.join(ANIMATIONS_DIR, MOVIE_PREFIX, "Base.mp4")
FORCED_FRAME_DURATION = 0.02
TIME_RANGE = (0, 42)
INITIAL_PADDING = 27
@ -88,7 +88,7 @@ class Hand(ImageMobject):
def __init__(self, num, small = False, **kwargs):
Mobject2D.__init__(self, **kwargs)
path = os.path.join(
MOVIE_DIR, MOVIE_PREFIX, "images", "Hand%d.png"%num
ANIMATIONS_DIR, MOVIE_PREFIX, "images", "Hand%d.png"%num
)
invert = False
if not self.read_in_cached_attrs(path, invert):
@ -164,7 +164,7 @@ class SaveEachNumber(OverHand):
OverHand.construct(self)
for count in COUNT_TO_FRAME_NUM:
path = os.path.join(
MOVIE_DIR, MOVIE_PREFIX, "images",
ANIMATIONS_DIR, MOVIE_PREFIX, "images",
"Hand%d.png"%count
)
Image.fromarray(self.frames[COUNT_TO_FRAME_NUM[count]]).save(path)

View file

@ -55,7 +55,7 @@ class Hand(ImageMobject):
def __init__(self, num, **kwargs):
Mobject2D.__init__(self, **kwargs)
path = os.path.join(
MOVIE_DIR, MOVIE_PREFIX, "images", "Hand%d.png"%num
ANIMATIONS_DIR, MOVIE_PREFIX, "images", "Hand%d.png"%num
)
invert = False
if self.read_in_cached_attrs(path, invert):
@ -81,14 +81,14 @@ class EdgeDetection(SceneFromVideo):
return "-".join([filename.split(".")[0], str(t1), str(t2)])
def construct(self, filename, t1, t2):
path = os.path.join(MOVIE_DIR, filename)
path = os.path.join(ANIMATIONS_DIR, filename)
SceneFromVideo.construct(self, path)
self.apply_gaussian_blur()
self.apply_edge_detection(t1, t2)
class BufferedCounting(SceneFromVideo):
def construct(self):
path = os.path.join(MOVIE_DIR, "CountingInBinary.m4v")
path = os.path.join(ANIMATIONS_DIR, "CountingInBinary.m4v")
time_range = (3, 42)
SceneFromVideo.construct(self, path, time_range = time_range)
self.buffer_pixels(spreads = (3, 2))
@ -128,7 +128,7 @@ class ClearLeftSide(SceneFromVideo):
return scenename
def construct(self, scenename):
path = os.path.join(MOVIE_DIR, MOVIE_PREFIX, scenename + ".mp4")
path = os.path.join(ANIMATIONS_DIR, MOVIE_PREFIX, scenename + ".mp4")
SceneFromVideo.construct(self, path)
self.highlight_region_over_time_range(
Region(lambda x, y : x < -1, shape = self.shape)
@ -146,7 +146,7 @@ class DraggedPixels(SceneFromVideo):
return args[0]
def construct(self, video):
path = os.path.join(MOVIE_DIR, MOVIE_PREFIX, video+".mp4")
path = os.path.join(ANIMATIONS_DIR, MOVIE_PREFIX, video+".mp4")
SceneFromVideo.construct(self, path)
self.drag_pixels()
@ -163,11 +163,11 @@ class DraggedPixels(SceneFromVideo):
class SaveEachNumber(SceneFromVideo):
def construct(self):
path = os.path.join(MOVIE_DIR, MOVIE_PREFIX, "ClearLeftSideBufferedCounting.mp4")
path = os.path.join(ANIMATIONS_DIR, MOVIE_PREFIX, "ClearLeftSideBufferedCounting.mp4")
SceneFromVideo.construct(self, path)
for count in COUNT_TO_FRAME_NUM:
path = os.path.join(
MOVIE_DIR, MOVIE_PREFIX, "images",
ANIMATIONS_DIR, MOVIE_PREFIX, "images",
"Hand%d.png"%count
)
Image.fromarray(self.frames[COUNT_TO_FRAME_NUM[count]]).save(path)
@ -182,7 +182,7 @@ class ShowCounting(SceneFromVideo):
return filename
def construct(self, filename):
path = os.path.join(MOVIE_DIR, MOVIE_PREFIX, filename + ".mp4")
path = os.path.join(ANIMATIONS_DIR, MOVIE_PREFIX, filename + ".mp4")
SceneFromVideo.construct(self, path)
total_time = len(self.frames)*self.frame_duration
for count in range(32):
@ -207,7 +207,7 @@ class ShowFrameNum(SceneFromVideo):
return filename
def construct(self, filename):
path = os.path.join(MOVIE_DIR, MOVIE_PREFIX, filename+".mp4")
path = os.path.join(ANIMATIONS_DIR, MOVIE_PREFIX, filename+".mp4")
SceneFromVideo.construct(self, path)
for frame, count in zip(self.frames, it.count()):
print(count + "of" + len(self.frames))

View file

@ -32,7 +32,7 @@ class Scene(object):
"save_frames" : False,
"save_pngs" : False,
"pngs_mode" : "RGBA",
"output_directory" : MOVIE_DIR,
"output_directory" : ANIMATIONS_DIR,
"name" : None,
"always_continually_update" : False,
"random_seed" : 0,

View file

@ -1,50 +0,0 @@
import sys
import inspect
import os
import shutil
import itertools as it
from extract_scene import is_scene, get_module
from constants import MOVIE_DIR, STAGED_SCENES_DIR
def get_sorted_scene_names(module_name):
module = get_module(module_name)
line_to_scene = {}
for name, scene_class in inspect.getmembers(module, is_scene):
lines, line_no = inspect.getsourcelines(scene_class)
line_to_scene[line_no] = name
return [
line_to_scene[line_no]
for line_no in sorted(line_to_scene.keys())
]
def stage_animaions(module_name):
scene_names = get_sorted_scene_names(module_name)
movie_dir = os.path.join(
MOVIE_DIR, module_name.replace(".py", "")
)
files = os.listdir(movie_dir)
sorted_files = []
for scene in scene_names:
for clip in filter(lambda f : f.startswith(scene), files):
sorted_files.append(
os.path.join(movie_dir, clip)
)
for f in os.listdir(STAGED_SCENES_DIR):
os.remove(os.path.join(STAGED_SCENES_DIR, f))
for f, count in zip(sorted_files, it.count()):
symlink_name = os.path.join(
STAGED_SCENES_DIR,
"Scene_%03d"%count + f.split(os.sep)[-1]
)
os.symlink(f, symlink_name)
if __name__ == "__main__":
if len(sys.argv) < 2:
raise Exception("No module given.")
module_name = sys.argv[1]
stage_animaions(module_name)

View file

@ -4,7 +4,7 @@ import os
import shutil
import itertools as it
from extract_scene import is_scene, get_module
from constants import MOVIE_DIR, STAGED_SCENES_DIR
from constants import ANIMATIONS_DIR, STAGED_SCENES_DIR
def get_sorted_scene_names(module_name):
@ -22,15 +22,15 @@ def get_sorted_scene_names(module_name):
def stage_animaions(module_name):
scene_names = get_sorted_scene_names(module_name)
movie_dir = os.path.join(
MOVIE_DIR, module_name.replace(".py", "")
animation_dir = os.path.join(
ANIMATIONS_DIR, module_name.replace(".py", "")
)
files = os.listdir(movie_dir)
files = os.listdir(animation_dir)
sorted_files = []
for scene in scene_names:
for clip in filter(lambda f : f.startswith(scene), files):
sorted_files.append(
os.path.join(movie_dir, clip)
os.path.join(animation_dir, clip)
)
for f in os.listdir(STAGED_SCENES_DIR):
os.remove(os.path.join(STAGED_SCENES_DIR, f))

View file

@ -13,7 +13,7 @@ from animation.simple_animations import Write, ShowCreation, AnimationGroup
from scene import Scene
PI_CREATURE_DIR = os.path.join(IMAGE_DIR, "PiCreature")
PI_CREATURE_DIR = os.path.join(MEDIA_DIR, "designs", "PiCreature")
PI_CREATURE_SCALE_FACTOR = 0.5
LEFT_EYE_INDEX = 0
@ -51,7 +51,7 @@ class PiCreature(SVGMobject):
except:
warnings.warn("No PiCreature design with mode %s"%mode)
svg_file = os.path.join(
PI_CREATURE_DIR,
FILE_DIR,
"PiCreatures_plain.svg"
)
SVGMobject.__init__(self, file_name = svg_file, **kwargs)

View file

@ -388,6 +388,10 @@ class Bubble(SVGMobject):
digest_config(self, kwargs, locals())
if self.file_name is None:
raise Exception("Must invoke Bubble subclass")
try:
SVGMobject.__init__(self, **kwargs)
except IOError as err:
self.file_name = os.path.join(FILE_DIR, self.file_name)
SVGMobject.__init__(self, **kwargs)
self.center()
self.stretch_to_fit_height(self.height)
@ -515,4 +519,22 @@ class Broadcast(LaggedStart):
self, ApplyMethod, circles,
lambda c : (c.restore,),
**kwargs
)