├── .gitignore ├── pystache ├── commands │ ├── __init__.py │ ├── test.py │ └── render.py ├── __init__.py ├── init.py ├── LICENSE ├── parsed.py ├── template_spec.py ├── defaults.py ├── common.py ├── specloader.py ├── locator.py ├── loader.py ├── renderengine.py ├── README.md ├── context.py ├── parser.py └── renderer.py ├── templates ├── fromleft-text.gif ├── appearing-text.gif ├── descending-text.gif ├── revealing-text.gif ├── appearing-text.xml ├── appearing-text.sif ├── descending-text.xml ├── fromleft-text.xml ├── descending-text.sif ├── fromleft-text.sif ├── revealing-text.xml └── revealing-text.sif ├── test ├── appearing-text_result.gif ├── fromleft-text_result.gif ├── revealing-text_result.gif ├── descending-text_result.gif ├── keyframe-import-test_v1.0.sifz ├── labels3.txt ├── labels2.txt ├── labels-multi.txt ├── labels.txt ├── out-commandline.sif ├── keyframe-import-test_out-with-no-ends.sif ├── keyframe-import-test_out-with-overwrite.sif ├── keyframe-import-test_out-with-ends.sif ├── keyframe-import-test_v0.61.sif ├── keyframe-import-test_v1.0.sif └── appearing-text_result.sif ├── plugin.xml ├── settings.py ├── README.md ├── synfig-import-labels.py └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /pystache/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO: add a docstring. 3 | 4 | """ 5 | -------------------------------------------------------------------------------- /templates/fromleft-text.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berteh/synfig-import-labels/HEAD/templates/fromleft-text.gif -------------------------------------------------------------------------------- /templates/appearing-text.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berteh/synfig-import-labels/HEAD/templates/appearing-text.gif -------------------------------------------------------------------------------- /templates/descending-text.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berteh/synfig-import-labels/HEAD/templates/descending-text.gif -------------------------------------------------------------------------------- /templates/revealing-text.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berteh/synfig-import-labels/HEAD/templates/revealing-text.gif -------------------------------------------------------------------------------- /test/appearing-text_result.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berteh/synfig-import-labels/HEAD/test/appearing-text_result.gif -------------------------------------------------------------------------------- /test/fromleft-text_result.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berteh/synfig-import-labels/HEAD/test/fromleft-text_result.gif -------------------------------------------------------------------------------- /test/revealing-text_result.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berteh/synfig-import-labels/HEAD/test/revealing-text_result.gif -------------------------------------------------------------------------------- /test/descending-text_result.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berteh/synfig-import-labels/HEAD/test/descending-text_result.gif -------------------------------------------------------------------------------- /test/keyframe-import-test_v1.0.sifz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berteh/synfig-import-labels/HEAD/test/keyframe-import-test_v1.0.sifz -------------------------------------------------------------------------------- /test/labels3.txt: -------------------------------------------------------------------------------- 1 | 0,100000 0,900000 short text 2 | 1,400000 2,871346 some longer text 3 | 1,966796 3,432578 again some loooong text 4 | 4,307196 4,807196 another short 5 | -------------------------------------------------------------------------------- /test/labels2.txt: -------------------------------------------------------------------------------- 1 | 0,000000 0,900000 part one 2 | 1,000000 1,871346 part two 3 | 1,966796 2,432578 part three 4 | 2,507196 2,807196 part four 5 | 3,232578 3,712881 part five 6 | 4,255000 4,355000 part six 7 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Import Labels and Timings 4 | Importer Textes et Temps Clés 5 | synfig-import-labels.py 6 | -------------------------------------------------------------------------------- /test/labels-multi.txt: -------------------------------------------------------------------------------- 1 | start stop text template 2 | 0,000000 0,900000 part one 3 | 1,000000 1,871346 part two descending-text 4 | 1,966796 2,432578 part three fromleft-text 5 | 2,507196 2,807196 part four 6 | 3,232578 3,712881 part five revealing-text 7 | 4,255000 4,355000 part six -------------------------------------------------------------------------------- /test/labels.txt: -------------------------------------------------------------------------------- 1 | 0,100000 0,900000 part 1 2 | 0,400000 0,871346 highlight in part 1 3 | 0,966796 1,432578 down in part 1 4 | 1,307196 1,807196 some instantaneous event 5 | 1,532578 1,712881 check for overwrite 6 | 3,255000 3,355000 check for minutes 7 | 3,050000 3,749000 check for hours 8 | -------------------------------------------------------------------------------- /pystache/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | TODO: add a docstring. 4 | 5 | """ 6 | 7 | # We keep all initialization code in a separate module. 8 | 9 | from pystache.init import parse, render, Renderer, TemplateSpec 10 | 11 | __all__ = ['parse', 'render', 'Renderer', 'TemplateSpec'] 12 | 13 | __version__ = '0.5.4' # Also change in setup.py. 14 | -------------------------------------------------------------------------------- /pystache/commands/test.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | This module provides a command to test pystache (unit tests, doctests, etc). 5 | 6 | """ 7 | 8 | import sys 9 | 10 | from pystache.tests.main import main as run_tests 11 | 12 | 13 | def main(sys_argv=sys.argv): 14 | run_tests(sys_argv=sys_argv) 15 | 16 | 17 | if __name__=='__main__': 18 | main() 19 | -------------------------------------------------------------------------------- /pystache/init.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | """ 4 | This module contains the initialization logic called by __init__.py. 5 | 6 | """ 7 | 8 | from pystache.parser import parse 9 | from pystache.renderer import Renderer 10 | from pystache.template_spec import TemplateSpec 11 | 12 | 13 | def render(template, context=None, **kwargs): 14 | """ 15 | Return the given template string rendered using the given context. 16 | 17 | """ 18 | renderer = Renderer() 19 | return renderer.render(template, context, **kwargs) 20 | -------------------------------------------------------------------------------- /test/out-commandline.sif: -------------------------------------------------------------------------------- 1 | 2 | 3 | Synfig Test for Keyframe import 4 | check for overwrite 5 | part 1highlight in part 1down in part 1some instantaneous eventcheck for minutescheck for hours 6 | -------------------------------------------------------------------------------- /test/keyframe-import-test_out-with-no-ends.sif: -------------------------------------------------------------------------------- 1 | 2 | 3 | Synfig Test for Keyframe import 4 | check for overwrite 5 | part 1 6 | highlight in part 1 7 | some instantaneous event 8 | down in part 1 9 | check for minutes 10 | check for hours 11 | 12 | -------------------------------------------------------------------------------- /test/keyframe-import-test_out-with-overwrite.sif: -------------------------------------------------------------------------------- 1 | 2 | 3 | Synfig Test for Keyframe import 4 | part 1 5 | highlight in part 1 6 | some instantaneous event 7 | check for overwrite 8 | down in part 1 9 | check for minutes 10 | check for hours 11 | 12 | -------------------------------------------------------------------------------- /test/keyframe-import-test_out-with-ends.sif: -------------------------------------------------------------------------------- 1 | 2 | 3 | Synfig Test for Keyframe import 4 | check for overwrite 5 | part 1 6 | highlight in part 1 7 | highlight in part 1 - end 8 | some instantaneous event 9 | check for overwrite - end 10 | down in part 1 11 | down in part 1 - end 12 | part 1 - end 13 | check for minutes 14 | check for hours 15 | 16 | -------------------------------------------------------------------------------- /pystache/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Chris Jerdonek. All rights reserved. 2 | 3 | Copyright (c) 2009 Chris Wanstrath 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /pystache/parsed.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | Exposes a class that represents a parsed (or compiled) template. 5 | 6 | """ 7 | 8 | 9 | class ParsedTemplate(object): 10 | 11 | """ 12 | Represents a parsed or compiled template. 13 | 14 | An instance wraps a list of unicode strings and node objects. A node 15 | object must have a `render(engine, stack)` method that accepts a 16 | RenderEngine instance and a ContextStack instance and returns a unicode 17 | string. 18 | 19 | """ 20 | 21 | def __init__(self): 22 | self._parse_tree = [] 23 | 24 | def __repr__(self): 25 | return repr(self._parse_tree) 26 | 27 | def add(self, node): 28 | """ 29 | Arguments: 30 | 31 | node: a unicode string or node object instance. See the class 32 | docstring for information. 33 | 34 | """ 35 | self._parse_tree.append(node) 36 | 37 | def render(self, engine, context): 38 | """ 39 | Returns: a string of type unicode. 40 | 41 | """ 42 | # We avoid use of the ternary operator for Python 2.4 support. 43 | def get_unicode(node): 44 | if type(node) is str: 45 | return node 46 | return node.render(engine, context) 47 | parts = list(map(get_unicode, self._parse_tree)) 48 | s = ''.join(parts) 49 | 50 | return str(s) 51 | -------------------------------------------------------------------------------- /pystache/template_spec.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | Provides a class to customize template information on a per-view basis. 5 | 6 | To customize template properties for a particular view, create that view 7 | from a class that subclasses TemplateSpec. The "spec" in TemplateSpec 8 | stands for "special" or "specified" template information. 9 | 10 | """ 11 | 12 | class TemplateSpec(object): 13 | 14 | """ 15 | A mixin or interface for specifying custom template information. 16 | 17 | The "spec" in TemplateSpec can be taken to mean that the template 18 | information is either "specified" or "special." 19 | 20 | A view should subclass this class only if customized template loading 21 | is needed. The following attributes allow one to customize/override 22 | template information on a per view basis. A None value means to use 23 | default behavior for that value and perform no customization. All 24 | attributes are initialized to None. 25 | 26 | Attributes: 27 | 28 | template: the template as a string. 29 | 30 | template_encoding: the encoding used by the template. 31 | 32 | template_extension: the template file extension. Defaults to "mustache". 33 | Pass False for no extension (i.e. extensionless template files). 34 | 35 | template_name: the name of the template. 36 | 37 | template_path: absolute path to the template. 38 | 39 | template_rel_directory: the directory containing the template file, 40 | relative to the directory containing the module defining the class. 41 | 42 | template_rel_path: the path to the template file, relative to the 43 | directory containing the module defining the class. 44 | 45 | """ 46 | 47 | template = None 48 | template_encoding = None 49 | template_extension = None 50 | template_name = None 51 | template_path = None 52 | template_rel_directory = None 53 | template_rel_path = None 54 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | # configuration for keyframes import 2 | # 3 | LABELS_FILE = "labels.txt" # audacity labels file name, must be located in your synfig project directory 4 | IMPORT_START = True # set to True to import keyframe for start of label 5 | IMPORT_END = False # set to True to import keyframe for end of label 6 | START_SUFFIX = "" # suffix to add to a label-start keyframe, to distinguish it from label-end frame 7 | END_SUFFIX = " - end" # suffix to add to a label-end keyframe, to distinguish it from label-start frame 8 | OVERWRITE_KEYFRAMES_WITH_SAME_NAME = False # set to True to replace keyframe with exact same description 9 | GENERATE_OBJECTS = True # set to True to generate objects (such as text layers) for each label 10 | # 11 | # settings below only matter to object generation. don't bother if GENERATE_OBJECTS is False. 12 | TEMPLATE_NAME = "appearing-text" # the name of template you want to use. must be located in templates/ subdirectory, with .xml extension. default is "popping-text" 13 | SPLIT_WORDS = False # split each word in a separate object 14 | WAYPOINT_TYPE = "halt" # one of: constant, auto, linear, clamped, halt 15 | RANDOM_ORIGIN = 70 # set to a percentage [0-100] to randomize the object origin in the whole document viewbox (0 will stack them all at [0,0]) 16 | ANIMATION_INTERVAL = 0.5 # interval (before and after the label time) used for (in & out) transition, in seconds. default is 0.5 17 | # 18 | #################### 19 | # 20 | # changing the values behind this point is not recommended as it may break the examples provided in the templates/ directory... 21 | # but if you still do, then you know what you're doing ;) 22 | # 23 | # values for "loop" based transitions (appearing,...) 24 | LOOP_BEFORE = "0.0" 25 | LOOP_MIDDLE = "1.0" 26 | LOOP_AFTER = "0.3" 27 | # values for "progressions" transitions (descending, revealing, fromleft...) 28 | VALUE_BEFORE = "-1.0" 29 | VALUE_MIDDLE = "0.0" 30 | VALUE_AFTER = "1.0" 31 | # location for plugin templates 32 | TEMPLATE_DIR = "templates" 33 | # labels parsing 34 | TSV_SEPARATOR = "\t" # single character separating data fields in LABELS_FILE 35 | HEADER = ["start","stop","text","template"] -------------------------------------------------------------------------------- /pystache/defaults.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | This module provides a central location for defining default behavior. 5 | 6 | Throughout the package, these defaults take effect only when the user 7 | does not otherwise specify a value. 8 | 9 | """ 10 | 11 | try: 12 | # Python 3.2 adds html.escape() and deprecates cgi.escape(). 13 | from html import escape 14 | except ImportError: 15 | from cgi import escape 16 | 17 | import os 18 | import sys 19 | 20 | from pystache.common import MissingTags 21 | 22 | 23 | # How to handle encoding errors when decoding strings from str to unicode. 24 | # 25 | # This value is passed as the "errors" argument to Python's built-in 26 | # unicode() function: 27 | # 28 | # http://docs.python.org/library/functions.html#unicode 29 | # 30 | DECODE_ERRORS = 'strict' 31 | 32 | # The name of the encoding to use when converting to unicode any strings of 33 | # type str encountered during the rendering process. 34 | STRING_ENCODING = sys.getdefaultencoding() 35 | 36 | # The name of the encoding to use when converting file contents to unicode. 37 | # This default takes precedence over the STRING_ENCODING default for 38 | # strings that arise from files. 39 | FILE_ENCODING = sys.getdefaultencoding() 40 | 41 | # The delimiters to start with when parsing. 42 | DELIMITERS = ('{{', '}}') 43 | 44 | # How to handle missing tags when rendering a template. 45 | MISSING_TAGS = MissingTags.ignore 46 | 47 | # The starting list of directories in which to search for templates when 48 | # loading a template by file name. 49 | SEARCH_DIRS = [os.curdir] # i.e. ['.'] 50 | 51 | # The escape function to apply to strings that require escaping when 52 | # rendering templates (e.g. for tags enclosed in double braces). 53 | # Only unicode strings will be passed to this function. 54 | # 55 | # The quote=True argument causes double but not single quotes to be escaped 56 | # in Python 3.1 and earlier, and both double and single quotes to be 57 | # escaped in Python 3.2 and later: 58 | # 59 | # http://docs.python.org/library/cgi.html#cgi.escape 60 | # http://docs.python.org/dev/library/html.html#html.escape 61 | # 62 | TAG_ESCAPE = lambda u: escape(u, quote=True) 63 | 64 | # The default template extension, without the leading dot. 65 | TEMPLATE_EXTENSION = 'mustache' 66 | -------------------------------------------------------------------------------- /pystache/common.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | Exposes functionality needed throughout the project. 5 | 6 | """ 7 | 8 | from sys import version_info 9 | 10 | def _get_string_types(): 11 | # TODO: come up with a better solution for this. One of the issues here 12 | # is that in Python 3 there is no common base class for unicode strings 13 | # and byte strings, and 2to3 seems to convert all of "str", "unicode", 14 | # and "basestring" to Python 3's "str". 15 | if version_info < (3, ): 16 | return str 17 | # The latter evaluates to "bytes" in Python 3 -- even after conversion by 2to3. 18 | return (str, type("a".encode('utf-8'))) 19 | 20 | 21 | _STRING_TYPES = _get_string_types() 22 | 23 | 24 | def is_string(obj): 25 | """ 26 | Return whether the given object is a byte string or unicode string. 27 | 28 | This function is provided for compatibility with both Python 2 and 3 29 | when using 2to3. 30 | 31 | """ 32 | return isinstance(obj, _STRING_TYPES) 33 | 34 | 35 | # This function was designed to be portable across Python versions -- both 36 | # with older versions and with Python 3 after applying 2to3. 37 | def read(path): 38 | """ 39 | Return the contents of a text file as a byte string. 40 | 41 | """ 42 | # Opening in binary mode is necessary for compatibility across Python 43 | # 2 and 3. In both Python 2 and 3, open() defaults to opening files in 44 | # text mode. However, in Python 2, open() returns file objects whose 45 | # read() method returns byte strings (strings of type `str` in Python 2), 46 | # whereas in Python 3, the file object returns unicode strings (strings 47 | # of type `str` in Python 3). 48 | f = open(path, 'rb') 49 | # We avoid use of the with keyword for Python 2.4 support. 50 | try: 51 | return f.read() 52 | finally: 53 | f.close() 54 | 55 | 56 | class MissingTags(object): 57 | 58 | """Contains the valid values for Renderer.missing_tags.""" 59 | 60 | ignore = 'ignore' 61 | strict = 'strict' 62 | 63 | 64 | class PystacheError(Exception): 65 | """Base class for Pystache exceptions.""" 66 | pass 67 | 68 | 69 | class TemplateNotFoundError(PystacheError): 70 | """An exception raised when a template is not found.""" 71 | pass 72 | -------------------------------------------------------------------------------- /templates/appearing-text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {{text}} 26 | 27 | 28 | 29 | 0.300000 30 | 0.000000 31 | 0.000000 32 | 1.000000 33 | 34 | 35 | 36 | Sans Serif 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 0.2500000000 53 | 0.2500000000 54 | 55 | 56 | 57 | 58 | 0.5000000000 59 | 0.5000000000 60 | 61 | 62 | 63 | 64 | {{origin_x}} 65 | {{origin_y}} 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /pystache/commands/render.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | This module provides command-line access to pystache. 5 | 6 | Run this script using the -h option for command-line help. 7 | 8 | """ 9 | 10 | 11 | try: 12 | import json 13 | except: 14 | # The json module is new in Python 2.6, whereas simplejson is 15 | # compatible with earlier versions. 16 | try: 17 | import simplejson as json 18 | except ImportError: 19 | # Raise an error with a type different from ImportError as a hack around 20 | # this issue: 21 | # http://bugs.python.org/issue7559 22 | from sys import exc_info 23 | ex_type, ex_value, tb = exc_info() 24 | new_ex = Exception("%s: %s" % (ex_type.__name__, ex_value)) 25 | raise new_ex.__class__(new_ex).with_traceback(tb) 26 | 27 | # The optparse module is deprecated in Python 2.7 in favor of argparse. 28 | # However, argparse is not available in Python 2.6 and earlier. 29 | from optparse import OptionParser 30 | import sys 31 | 32 | # We use absolute imports here to allow use of this script from its 33 | # location in source control (e.g. for development purposes). 34 | # Otherwise, the following error occurs: 35 | # 36 | # ValueError: Attempted relative import in non-package 37 | # 38 | from pystache.common import TemplateNotFoundError 39 | from pystache.renderer import Renderer 40 | 41 | 42 | USAGE = """\ 43 | %prog [-h] template context 44 | 45 | Render a mustache template with the given context. 46 | 47 | positional arguments: 48 | template A filename or template string. 49 | context A filename or JSON string.""" 50 | 51 | 52 | def parse_args(sys_argv, usage): 53 | """ 54 | Return an OptionParser for the script. 55 | 56 | """ 57 | args = sys_argv[1:] 58 | 59 | parser = OptionParser(usage=usage) 60 | options, args = parser.parse_args(args) 61 | 62 | template, context = args 63 | 64 | return template, context 65 | 66 | 67 | # TODO: verify whether the setup() method's entry_points argument 68 | # supports passing arguments to main: 69 | # 70 | # http://packages.python.org/distribute/setuptools.html#automatic-script-creation 71 | # 72 | def main(sys_argv=sys.argv): 73 | template, context = parse_args(sys_argv, USAGE) 74 | 75 | if template.endswith('.mustache'): 76 | template = template[:-9] 77 | 78 | renderer = Renderer() 79 | 80 | try: 81 | template = renderer.load_template(template) 82 | except TemplateNotFoundError: 83 | pass 84 | 85 | try: 86 | context = json.load(open(context)) 87 | except IOError: 88 | context = json.loads(context) 89 | 90 | rendered = renderer.render(template, context) 91 | print(rendered) 92 | 93 | 94 | if __name__=='__main__': 95 | main() 96 | -------------------------------------------------------------------------------- /pystache/specloader.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | This module supports customized (aka special or specified) template loading. 5 | 6 | """ 7 | 8 | import os.path 9 | 10 | from pystache.loader import Loader 11 | 12 | 13 | # TODO: add test cases for this class. 14 | class SpecLoader(object): 15 | 16 | """ 17 | Supports loading custom-specified templates (from TemplateSpec instances). 18 | 19 | """ 20 | 21 | def __init__(self, loader=None): 22 | if loader is None: 23 | loader = Loader() 24 | 25 | self.loader = loader 26 | 27 | def _find_relative(self, spec): 28 | """ 29 | Return the path to the template as a relative (dir, file_name) pair. 30 | 31 | The directory returned is relative to the directory containing the 32 | class definition of the given object. The method returns None for 33 | this directory if the directory is unknown without first searching 34 | the search directories. 35 | 36 | """ 37 | if spec.template_rel_path is not None: 38 | return os.path.split(spec.template_rel_path) 39 | # Otherwise, determine the file name separately. 40 | 41 | locator = self.loader._make_locator() 42 | 43 | # We do not use the ternary operator for Python 2.4 support. 44 | if spec.template_name is not None: 45 | template_name = spec.template_name 46 | else: 47 | template_name = locator.make_template_name(spec) 48 | 49 | file_name = locator.make_file_name(template_name, spec.template_extension) 50 | 51 | return (spec.template_rel_directory, file_name) 52 | 53 | def _find(self, spec): 54 | """ 55 | Find and return the path to the template associated to the instance. 56 | 57 | """ 58 | if spec.template_path is not None: 59 | return spec.template_path 60 | 61 | dir_path, file_name = self._find_relative(spec) 62 | 63 | locator = self.loader._make_locator() 64 | 65 | if dir_path is None: 66 | # Then we need to search for the path. 67 | path = locator.find_object(spec, self.loader.search_dirs, file_name=file_name) 68 | else: 69 | obj_dir = locator.get_object_directory(spec) 70 | path = os.path.join(obj_dir, dir_path, file_name) 71 | 72 | return path 73 | 74 | def load(self, spec): 75 | """ 76 | Find and return the template associated to a TemplateSpec instance. 77 | 78 | Returns the template as a unicode string. 79 | 80 | Arguments: 81 | 82 | spec: a TemplateSpec instance. 83 | 84 | """ 85 | if spec.template is not None: 86 | return self.loader.str(spec.template, spec.template_encoding) 87 | 88 | path = self._find(spec) 89 | 90 | return self.loader.read(path, spec.template_encoding) 91 | -------------------------------------------------------------------------------- /templates/appearing-text.sif: -------------------------------------------------------------------------------- 1 | 2 | 3 | Appearing text template for Keyframe import in SynfigStudio 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | popping text goes here 40 | 41 | 42 | 43 | 0.300000 44 | 0.000000 45 | 0.000000 46 | 1.000000 47 | 48 | 49 | 50 | Sans Serif 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 0.2500000000 67 | 0.2500000000 68 | 69 | 70 | 71 | 72 | 0.5000000000 73 | 0.5000000000 74 | 75 | 76 | 77 | 78 | 0.0000000000 79 | 0.0000000000 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /templates/descending-text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {{text}} 13 | 14 | 15 | 16 | 0.033015 17 | 0.015800 18 | 0.203155 19 | 1.000000 20 | 21 | 22 | 23 | Sans Serif 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 0.3333333433 42 | 0.3333333433 43 | 44 | 45 | 46 | 47 | 48 | 49 | -0.5000000000 50 | -0.5000000000 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 0.5000000000 92 | 0.5000000000 93 | 94 | 95 | 96 | 97 | 98 | 99 | 0.0000000000 100 | 0.0000000000 101 | 102 | 103 | 104 | 105 | 0.0000000000 106 | -2.5000000000 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /templates/fromleft-text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 0.0000000000 14 | {{origin_y}} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0.0000000000 22 | 0.0000000000 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 1.0000000000 34 | 1.0000000000 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | {{text}} 53 | 54 | 55 | 56 | 0.000000 57 | 0.082076 58 | 0.392097 59 | 1.000000 60 | 61 | 62 | 63 | Sans Serif 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 0.2500000000 80 | 0.2500000000 81 | 82 | 83 | 84 | 85 | 0.0000000000 86 | 0.5000000000 87 | 88 | 89 | 90 | 91 | 92 | 93 | -4.0000000000 94 | 0.0000000000 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 143 | -------------------------------------------------------------------------------- /pystache/locator.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | This module provides a Locator class for finding template files. 5 | 6 | """ 7 | 8 | import os 9 | import re 10 | import sys 11 | 12 | from pystache.common import TemplateNotFoundError 13 | from pystache import defaults 14 | 15 | 16 | class Locator(object): 17 | 18 | def __init__(self, extension=None): 19 | """ 20 | Construct a template locator. 21 | 22 | Arguments: 23 | 24 | extension: the template file extension, without the leading dot. 25 | Pass False for no extension (e.g. to use extensionless template 26 | files). Defaults to the package default. 27 | 28 | """ 29 | if extension is None: 30 | extension = defaults.TEMPLATE_EXTENSION 31 | 32 | self.template_extension = extension 33 | 34 | def get_object_directory(self, obj): 35 | """ 36 | Return the directory containing an object's defining class. 37 | 38 | Returns None if there is no such directory, for example if the 39 | class was defined in an interactive Python session, or in a 40 | doctest that appears in a text file (rather than a Python file). 41 | 42 | """ 43 | if not hasattr(obj, '__module__'): 44 | return None 45 | 46 | module = sys.modules[obj.__module__] 47 | 48 | if not hasattr(module, '__file__'): 49 | # TODO: add a unit test for this case. 50 | return None 51 | 52 | path = module.__file__ 53 | 54 | return os.path.dirname(path) 55 | 56 | def make_template_name(self, obj): 57 | """ 58 | Return the canonical template name for an object instance. 59 | 60 | This method converts Python-style class names (PEP 8's recommended 61 | CamelCase, aka CapWords) to lower_case_with_underscords. Here 62 | is an example with code: 63 | 64 | >>> class HelloWorld(object): 65 | ... pass 66 | >>> hi = HelloWorld() 67 | >>> 68 | >>> locator = Locator() 69 | >>> locator.make_template_name(hi) 70 | 'hello_world' 71 | 72 | """ 73 | template_name = obj.__class__.__name__ 74 | 75 | def repl(match): 76 | return '_' + match.group(0).lower() 77 | 78 | return re.sub('[A-Z]', repl, template_name)[1:] 79 | 80 | def make_file_name(self, template_name, template_extension=None): 81 | """ 82 | Generate and return the file name for the given template name. 83 | 84 | Arguments: 85 | 86 | template_extension: defaults to the instance's extension. 87 | 88 | """ 89 | file_name = template_name 90 | 91 | if template_extension is None: 92 | template_extension = self.template_extension 93 | 94 | if template_extension is not False: 95 | file_name += os.path.extsep + template_extension 96 | 97 | return file_name 98 | 99 | def _find_path(self, search_dirs, file_name): 100 | """ 101 | Search for the given file, and return the path. 102 | 103 | Returns None if the file is not found. 104 | 105 | """ 106 | for dir_path in search_dirs: 107 | file_path = os.path.join(dir_path, file_name) 108 | if os.path.exists(file_path): 109 | return file_path 110 | 111 | return None 112 | 113 | def _find_path_required(self, search_dirs, file_name): 114 | """ 115 | Return the path to a template with the given file name. 116 | 117 | """ 118 | path = self._find_path(search_dirs, file_name) 119 | 120 | if path is None: 121 | raise TemplateNotFoundError('File %s not found in dirs: %s' % 122 | (repr(file_name), repr(search_dirs))) 123 | 124 | return path 125 | 126 | def find_file(self, file_name, search_dirs): 127 | """ 128 | Return the path to a template with the given file name. 129 | 130 | Arguments: 131 | 132 | file_name: the file name of the template. 133 | 134 | search_dirs: the list of directories in which to search. 135 | 136 | """ 137 | return self._find_path_required(search_dirs, file_name) 138 | 139 | def find_name(self, template_name, search_dirs): 140 | """ 141 | Return the path to a template with the given name. 142 | 143 | Arguments: 144 | 145 | template_name: the name of the template. 146 | 147 | search_dirs: the list of directories in which to search. 148 | 149 | """ 150 | file_name = self.make_file_name(template_name) 151 | 152 | return self._find_path_required(search_dirs, file_name) 153 | 154 | def find_object(self, obj, search_dirs, file_name=None): 155 | """ 156 | Return the path to a template associated with the given object. 157 | 158 | """ 159 | if file_name is None: 160 | # TODO: should we define a make_file_name() method? 161 | template_name = self.make_template_name(obj) 162 | file_name = self.make_file_name(template_name) 163 | 164 | dir_path = self.get_object_directory(obj) 165 | 166 | if dir_path is not None: 167 | search_dirs = [dir_path] + search_dirs 168 | 169 | path = self._find_path_required(search_dirs, file_name) 170 | 171 | return path 172 | -------------------------------------------------------------------------------- /pystache/loader.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | This module provides a Loader class for locating and reading templates. 5 | 6 | """ 7 | 8 | import os 9 | import sys 10 | 11 | from pystache import common 12 | from pystache import defaults 13 | from pystache.locator import Locator 14 | 15 | 16 | # We make a function so that the current defaults take effect. 17 | # TODO: revisit whether this is necessary. 18 | 19 | def _make_to_unicode(): 20 | def to_unicode(s, encoding=None): 21 | """ 22 | Raises a TypeError exception if the given string is already unicode. 23 | 24 | """ 25 | if encoding is None: 26 | encoding = defaults.STRING_ENCODING 27 | return str(s, encoding, defaults.DECODE_ERRORS) 28 | return to_unicode 29 | 30 | 31 | class Loader(object): 32 | 33 | """ 34 | Loads the template associated to a name or user-defined object. 35 | 36 | All load_*() methods return the template as a unicode string. 37 | 38 | """ 39 | 40 | def __init__(self, file_encoding=None, extension=None, to_unicode=None, 41 | search_dirs=None): 42 | """ 43 | Construct a template loader instance. 44 | 45 | Arguments: 46 | 47 | extension: the template file extension, without the leading dot. 48 | Pass False for no extension (e.g. to use extensionless template 49 | files). Defaults to the package default. 50 | 51 | file_encoding: the name of the encoding to use when converting file 52 | contents to unicode. Defaults to the package default. 53 | 54 | search_dirs: the list of directories in which to search when loading 55 | a template by name or file name. Defaults to the package default. 56 | 57 | to_unicode: the function to use when converting strings of type 58 | str to unicode. The function should have the signature: 59 | 60 | to_unicode(s, encoding=None) 61 | 62 | It should accept a string of type str and an optional encoding 63 | name and return a string of type unicode. Defaults to calling 64 | Python's built-in function unicode() using the package string 65 | encoding and decode errors defaults. 66 | 67 | """ 68 | if extension is None: 69 | extension = defaults.TEMPLATE_EXTENSION 70 | 71 | if file_encoding is None: 72 | file_encoding = defaults.FILE_ENCODING 73 | 74 | if search_dirs is None: 75 | search_dirs = defaults.SEARCH_DIRS 76 | 77 | if to_unicode is None: 78 | to_unicode = _make_to_unicode() 79 | 80 | self.extension = extension 81 | self.file_encoding = file_encoding 82 | # TODO: unit test setting this attribute. 83 | self.search_dirs = search_dirs 84 | self.to_unicode = to_unicode 85 | 86 | def _make_locator(self): 87 | return Locator(extension=self.extension) 88 | 89 | def str(self, s, encoding=None): 90 | """ 91 | Convert a string to unicode using the given encoding, and return it. 92 | 93 | This function uses the underlying to_unicode attribute. 94 | 95 | Arguments: 96 | 97 | s: a basestring instance to convert to unicode. Unlike Python's 98 | built-in unicode() function, it is okay to pass unicode strings 99 | to this function. (Passing a unicode string to Python's unicode() 100 | with the encoding argument throws the error, "TypeError: decoding 101 | Unicode is not supported.") 102 | 103 | encoding: the encoding to pass to the to_unicode attribute. 104 | Defaults to None. 105 | 106 | """ 107 | if isinstance(s, str): 108 | return str(s) 109 | 110 | return self.to_unicode(s, encoding) 111 | 112 | def read(self, path, encoding=None): 113 | """ 114 | Read the template at the given path, and return it as a unicode string. 115 | 116 | """ 117 | b = common.read(path) 118 | 119 | if encoding is None: 120 | encoding = self.file_encoding 121 | 122 | return self.str(b, encoding) 123 | 124 | def load_file(self, file_name): 125 | """ 126 | Find and return the template with the given file name. 127 | 128 | Arguments: 129 | 130 | file_name: the file name of the template. 131 | 132 | """ 133 | locator = self._make_locator() 134 | 135 | path = locator.find_file(file_name, self.search_dirs) 136 | 137 | return self.read(path) 138 | 139 | def load_name(self, name): 140 | """ 141 | Find and return the template with the given template name. 142 | 143 | Arguments: 144 | 145 | name: the name of the template. 146 | 147 | """ 148 | locator = self._make_locator() 149 | 150 | path = locator.find_name(name, self.search_dirs) 151 | 152 | return self.read(path) 153 | 154 | # TODO: unit-test this method. 155 | def load_object(self, obj): 156 | """ 157 | Find and return the template associated to the given object. 158 | 159 | Arguments: 160 | 161 | obj: an instance of a user-defined class. 162 | 163 | search_dirs: the list of directories in which to search. 164 | 165 | """ 166 | locator = self._make_locator() 167 | 168 | path = locator.find_object(obj, self.search_dirs) 169 | 170 | return self.read(path) 171 | -------------------------------------------------------------------------------- /templates/descending-text.sif: -------------------------------------------------------------------------------- 1 | 2 | 3 | Descending text template for Keyframe import in SynfigStudio 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | descending text idea 34 | 35 | 36 | 37 | 0.033015 38 | 0.015800 39 | 0.203155 40 | 1.000000 41 | 42 | 43 | 44 | Sans Serif 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 0.3333333433 63 | 0.3333333433 64 | 65 | 66 | 67 | 68 | 69 | 70 | -0.5000000000 71 | -0.5000000000 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 0.5000000000 113 | 0.5000000000 114 | 115 | 116 | 117 | 118 | 119 | 120 | 0.0000000000 121 | 0.0000000000 122 | 123 | 124 | 125 | 126 | 0.0000000000 127 | -2.5000000000 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /test/keyframe-import-test_v0.61.sif: -------------------------------------------------------------------------------- 1 | 2 | 3 | Synfig Test for Keyframe import 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 0.0000000000 31 | 0.0000000000 32 | 33 | 34 | 35 | 36 | 37 | 38 | 0.0000000000 39 | 0.0000000000 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 1.0000000000 51 | 1.0000000000 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 1.000000 71 | 1.000000 72 | 1.000000 73 | 1.000000 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | this animation has been generated automatically 89 | from an Audacity Labels file, with SynfigStudio. 90 | All Open Source animation 91 | 92 | 93 | 94 | 0.000001 95 | 0.000001 96 | 0.000001 97 | 1.000000 98 | 99 | 100 | 101 | Sans Serif 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 0.1333333403 118 | 0.1333333403 119 | 120 | 121 | 122 | 123 | 0.5000000000 124 | 0.5000000000 125 | 126 | 127 | 128 | 129 | 0.9415920973 130 | -1.7408642769 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 167 | 168 | -------------------------------------------------------------------------------- /templates/fromleft-text.sif: -------------------------------------------------------------------------------- 1 | 2 | 3 | Text flying from left template for Keyframe import in SynfigStudio 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 0.0000000000 33 | 0.0000000000 34 | 35 | 36 | 37 | 38 | 39 | 40 | 0.0000000000 41 | 0.0000000000 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 1.0000000000 53 | 1.0000000000 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | this text enters view from left 72 | 73 | 74 | 75 | 0.000000 76 | 0.082076 77 | 0.392097 78 | 1.000000 79 | 80 | 81 | 82 | Sans Serif 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 0.2500000000 99 | 0.2500000000 100 | 101 | 102 | 103 | 104 | 0.0000000000 105 | 0.5000000000 106 | 107 | 108 | 109 | 110 | 111 | 112 | -4.0000000000 113 | 0.0000000000 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 162 | 163 | -------------------------------------------------------------------------------- /test/keyframe-import-test_v1.0.sif: -------------------------------------------------------------------------------- 1 | 2 | 3 | Synfig Test for Keyframe import 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 0.0000000000 31 | 0.0000000000 32 | 33 | 34 | 35 | 36 | 37 | 38 | 0.0000000000 39 | 0.0000000000 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 1.0000000000 51 | 1.0000000000 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 1.000000 71 | 1.000000 72 | 1.000000 73 | 1.000000 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | this animation has been generated automatically 89 | from an Audacity Labels file, with SynfigStudio. 90 | All Open Source animation 91 | 92 | 93 | 94 | 0.000001 95 | 0.000001 96 | 0.000001 97 | 1.000000 98 | 99 | 100 | 101 | Sans Serif 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 0.1333333403 118 | 0.1333333403 119 | 120 | 121 | 122 | 123 | 0.5000000000 124 | 0.5000000000 125 | 126 | 127 | 128 | 129 | 0.9415920973 130 | -1.7408642769 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 170 | 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Import Labels and Timings in Synfig (Open Source 2D Animation Studio) 2 | ## - aka generate Keyframes and Kinetic Typography (artistic subtitles) 3 | 4 | A [Synfig](http://synfig.org) plug-in to create time reference and/or artistic kinetic text effects in Synfig, from [Audacity](http://audacity.sourceforge.net/) track labels (aka subtitles) or excel/openoffice data. 5 | 6 | Synfig Studio is a free and open-source 2D animation software, designed as powerful industrial-strength solution for creating film-quality animation using a vector and bitmap artwork. 7 | 8 | The generated time reference (keyframes) is great for synchronizing animation with your audio. 9 | 10 | The generated kinetic texts objects is a great basis for beautiful typographic animations, and can be customized easily to achieve your prefered effect(s). 11 | 12 | ## Result 13 | 14 | Different effects are readily available, and it is (rather) easy to create new ones. Check out [our gallery](https://github.com/berteh/synfig-import-labels/wiki/Gallery) for many examples, a typical one is descending text: 15 | 16 | ![descending text](test/descending-text_result.gif) 17 | 18 | A **demo animation** illustrating many more effects at once is online at https://www.youtube.com/watch?v=F4pDivKQf-g 19 | 20 | Using a simple white background and importing a set of 6 text lines with their timings the following is generated automatically: automatic keyframes (top-right), automatic objects (bottom-right) auto-animated (bottom-left), and ready to be fine-tuned in Synfig before exporting for the world to see ;) 21 | ![example in synfig](http://i61.tinypic.com/fa1x3.jpg) 22 | 23 | ## Use 24 | 25 | Run the script from within synfig: 26 | 27 | 1. open your synfig project 28 | 1. run the plugin at _> Plug-Ins > Import Labels and Timings_ 29 | 30 | or from the command-line: 31 | 32 | python synfig-import-labels.py in.sifz (labels.txt (out.sifz)) 33 | 34 | To create your own timed labels file you can: 35 | 36 | 1. use [Audacity to label track segments](http://multimedia.journalism.berkeley.edu/tutorials/audacity/adding-labels/) 37 | 1. export your labels via Tracks > Edit Labels > export, in Audacity. 38 | 39 | or simply use **Notepad** and create a table with 3 "columns": _start time (in sec)_ ``tab`` _end time (in sec)_``tab`` _text (single line)_. 40 | You can add, optionally, add a 4th column with the name of ``template`` to use for this text line and the following ones. Save the file as _labels.txt_ in the directory of your sifz Synfig file project. Titles in the first row are optional, just the data will do just fine, but if you use titles, they must be _exactly_ the following: 41 | 42 | start stop text template 43 | 0.500 1.50 first sentence showing for 1 second descending-text 44 | 1.50 1.90 cool ! 45 | 2.40 3.2784 your imagination is the limit ! revealing-text 46 | 47 | 48 | You can use any **spreadsheet** software that is able to export to tsv (_tab separated value_ file), such as OpenOffice or Excel, just make sure to save as UTF-8 if you need proper accents supports. 49 | 50 | Not mentioning any ``template`` column will simply use the default template (see [Configuration](#configuration)) 51 | 52 | ## How-to / Tutorial 53 | 54 | The use of this plugin to generate keyframes in a complete animation design is described in the [Synfig Audio Synchronisation tutorial](http://wiki.synfig.org/wiki/Doc:Audio_Synchronisation) 55 | 56 | A tutorial will be written on creating kynetic typographies one day... but I (not secretly) hope you beat me to it! 57 | 58 | ## Install 59 | 60 | Decompress [plugin archive](https://github.com/berteh/synfig-import-labels/archive/master.zip ) into your synfig plugins directory. 61 | 62 | In linux: ``home/-user-/.synfig/plugins``, 63 | in Windows: ``C:\Users\USERNAME\Synfig\plugins``, 64 | in OSX: ``/Users/USERNAME/Library/Synfig/plugins``. 65 | If you are using a portable version of synfig, you will find it at ``ROOT/share/synfig/plugins``. 66 | 67 | Another option is to clone [this repository](https://github.com/berteh/synfig-import-labels.git) in the same location. 68 | 69 | Requirements: Python (Synfig is a recommended option ;) - more info on [Synfig Plugins page](http://wiki.synfig.org/wiki/Doc:Plugins#How_to_install_plugins) 70 | 71 | More information on using plugins is available from the official [Scribus documentation](https://synfig.readthedocs.io/en/latest/plugins.html#how-to-install-plugins). 72 | 73 | ## Configuration 74 | 75 | edit `settings.py` for customisation: 76 | 77 | ```python 78 | # configuration for keyframes import 79 | # 80 | LABELS_FILE = "labels.txt" # audacity labels file name, must be located in your synfig project directory 81 | IMPORT_START = True # set to True to import keyframe for start of label 82 | IMPORT_END = False # set to True to import keyframe for end of label 83 | START_SUFFIX = "" # suffix to add to a label-start keyframe, to distinguish it from label-end frame 84 | END_SUFFIX = " - end" # suffix to add to a label-end keyframe, to distinguish it from label-start frame 85 | OVERWRITE_KEYFRAMES_WITH_SAME_NAME = False # set to True to replace keyframe with exact same description 86 | GENERATE_OBJECTS = True # set to True to generate objects (such as text layers) for each label 87 | # 88 | # settings below only matter to object generation. don't bother if GENERATE_OBJECTS is False. 89 | TEMPLATE_NAME = "appearing-text" # the name of template you want to use. must be located in templates/ subdirectory, with .xml extension. default is "popping-text" 90 | SPLIT_WORDS = False # split each word in a separate object 91 | WAYPOINT_TYPE = "halt" # one of: constant, auto, linear, clamped, halt 92 | RANDOM_ORIGIN = 70 # set to a percentage [0-100] to randomize the object origin in the whole document viewbox (0 will stack them all at [0,0]) 93 | ANIMATION_INTERVAL = 0.5 # interval (before and after the label time) used for (in & out) transition, in seconds. default is 0.5 94 | # 95 | ``` 96 | 97 | ## Contribution 98 | 99 | Got other ideas or templates? add them to the [wiki](/wiki/Gallery) or [create a pull request](https://help.github.com/articles/using-pull-requests/) to share them! 100 | 101 | 102 | ## Support 103 | Preferably use github's [issues tracker](https://github.com/berteh/synfig-import-labels/issues) for bug reports, feature requests and contributing to this code. 104 | 105 | ## Changelog 106 | 107 | - v0.1 - August 2014 - keyframe generation 108 | - v0.2 - September 2015 - objects generation 109 | - v0.3 - June 2016 - tiny improvements 110 | 111 | ## Licence 112 | GNU-GPL v2, same as [Synfig Studio](http://synfig.org). 113 | 114 | This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 115 | 116 | This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 117 | 118 | You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA or visit http://www.gnu.org/licenses/gpl.html 119 | -------------------------------------------------------------------------------- /pystache/renderengine.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | Defines a class responsible for rendering logic. 5 | 6 | """ 7 | 8 | import re 9 | 10 | from pystache.common import is_string 11 | from pystache.parser import parse 12 | import collections 13 | 14 | 15 | def context_get(stack, name): 16 | """ 17 | Find and return a name from a ContextStack instance. 18 | 19 | """ 20 | return stack.get(name) 21 | 22 | 23 | class RenderEngine(object): 24 | 25 | """ 26 | Provides a render() method. 27 | 28 | This class is meant only for internal use. 29 | 30 | As a rule, the code in this class operates on unicode strings where 31 | possible rather than, say, strings of type str or markupsafe.Markup. 32 | This means that strings obtained from "external" sources like partials 33 | and variable tag values are immediately converted to unicode (or 34 | escaped and converted to unicode) before being operated on further. 35 | This makes maintaining, reasoning about, and testing the correctness 36 | of the code much simpler. In particular, it keeps the implementation 37 | of this class independent of the API details of one (or possibly more) 38 | unicode subclasses (e.g. markupsafe.Markup). 39 | 40 | """ 41 | 42 | # TODO: it would probably be better for the constructor to accept 43 | # and set as an attribute a single RenderResolver instance 44 | # that encapsulates the customizable aspects of converting 45 | # strings and resolving partials and names from context. 46 | def __init__(self, literal=None, escape=None, resolve_context=None, 47 | resolve_partial=None, to_str=None): 48 | """ 49 | Arguments: 50 | 51 | literal: the function used to convert unescaped variable tag 52 | values to unicode, e.g. the value corresponding to a tag 53 | "{{{name}}}". The function should accept a string of type 54 | str or unicode (or a subclass) and return a string of type 55 | unicode (but not a proper subclass of unicode). 56 | This class will only pass basestring instances to this 57 | function. For example, it will call str() on integer variable 58 | values prior to passing them to this function. 59 | 60 | escape: the function used to escape and convert variable tag 61 | values to unicode, e.g. the value corresponding to a tag 62 | "{{name}}". The function should obey the same properties 63 | described above for the "literal" function argument. 64 | This function should take care to convert any str 65 | arguments to unicode just as the literal function should, as 66 | this class will not pass tag values to literal prior to passing 67 | them to this function. This allows for more flexibility, 68 | for example using a custom escape function that handles 69 | incoming strings of type markupsafe.Markup differently 70 | from plain unicode strings. 71 | 72 | resolve_context: the function to call to resolve a name against 73 | a context stack. The function should accept two positional 74 | arguments: a ContextStack instance and a name to resolve. 75 | 76 | resolve_partial: the function to call when loading a partial. 77 | The function should accept a template name string and return a 78 | template string of type unicode (not a subclass). 79 | 80 | to_str: a function that accepts an object and returns a string (e.g. 81 | the built-in function str). This function is used for string 82 | coercion whenever a string is required (e.g. for converting None 83 | or 0 to a string). 84 | 85 | """ 86 | self.escape = escape 87 | self.literal = literal 88 | self.resolve_context = resolve_context 89 | self.resolve_partial = resolve_partial 90 | self.to_str = to_str 91 | 92 | # TODO: Rename context to stack throughout this module. 93 | 94 | # From the spec: 95 | # 96 | # When used as the data value for an Interpolation tag, the lambda 97 | # MUST be treatable as an arity 0 function, and invoked as such. 98 | # The returned value MUST be rendered against the default delimiters, 99 | # then interpolated in place of the lambda. 100 | # 101 | def fetch_string(self, context, name): 102 | """ 103 | Get a value from the given context as a basestring instance. 104 | 105 | """ 106 | val = self.resolve_context(context, name) 107 | 108 | if isinstance(val, collections.Callable): 109 | # Return because _render_value() is already a string. 110 | return self._render_value(val(), context) 111 | 112 | if not is_string(val): 113 | return self.to_str(val) 114 | 115 | return val 116 | 117 | def fetch_section_data(self, context, name): 118 | """ 119 | Fetch the value of a section as a list. 120 | 121 | """ 122 | data = self.resolve_context(context, name) 123 | 124 | # From the spec: 125 | # 126 | # If the data is not of a list type, it is coerced into a list 127 | # as follows: if the data is truthy (e.g. `!!data == true`), 128 | # use a single-element list containing the data, otherwise use 129 | # an empty list. 130 | # 131 | if not data: 132 | data = [] 133 | else: 134 | # The least brittle way to determine whether something 135 | # supports iteration is by trying to call iter() on it: 136 | # 137 | # http://docs.python.org/library/functions.html#iter 138 | # 139 | # It is not sufficient, for example, to check whether the item 140 | # implements __iter__ () (the iteration protocol). There is 141 | # also __getitem__() (the sequence protocol). In Python 2, 142 | # strings do not implement __iter__(), but in Python 3 they do. 143 | try: 144 | iter(data) 145 | except TypeError: 146 | # Then the value does not support iteration. 147 | data = [data] 148 | else: 149 | if is_string(data) or isinstance(data, dict): 150 | # Do not treat strings and dicts (which are iterable) as lists. 151 | data = [data] 152 | # Otherwise, treat the value as a list. 153 | 154 | return data 155 | 156 | def _render_value(self, val, context, delimiters=None): 157 | """ 158 | Render an arbitrary value. 159 | 160 | """ 161 | if not is_string(val): 162 | # In case the template is an integer, for example. 163 | val = self.to_str(val) 164 | if type(val) is not str: 165 | val = self.literal(val) 166 | return self.render(val, context, delimiters) 167 | 168 | def render(self, template, context_stack, delimiters=None): 169 | """ 170 | Render a unicode template string, and return as unicode. 171 | 172 | Arguments: 173 | 174 | template: a template string of type unicode (but not a proper 175 | subclass of unicode). 176 | 177 | context_stack: a ContextStack instance. 178 | 179 | """ 180 | parsed_template = parse(template, delimiters) 181 | 182 | return parsed_template.render(self, context_stack) 183 | -------------------------------------------------------------------------------- /templates/revealing-text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{origin_x}} 14 | {{origin_y}} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0.0000000000 22 | 0.0000000000 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 1.0000000000 34 | 1.0000000000 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | {{text}} 53 | 54 | 55 | 56 | 0.074763 57 | 0.000000 58 | 0.094494 59 | 1.000000 60 | 61 | 62 | 63 | Sans Serif 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 0.2500000000 80 | 0.2500000000 81 | 82 | 83 | 84 | 85 | 0.5000000000 86 | 0.5000000000 87 | 88 | 89 | 90 | 91 | 0.0000000000 92 | 0.0000000000 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 0.0000000000 118 | 0.0000000000 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 4.0000000000 128 | 0.0000000000 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 1.0000000000 152 | 1.0000000000 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 0.0666666701 172 | 0.0000000000 173 | 174 | 175 | 176 | 177 | -0.0666666701 178 | 0.0000000000 179 | 180 | 181 | 182 | 183 | 184 | 1.000000 185 | 1.000000 186 | 1.000000 187 | 1.000000 188 | 189 | 190 | 0.041083 191 | 0.000000 192 | 0.000000 193 | 0.000000 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 228 | 229 | 230 | 231 | 252 | -------------------------------------------------------------------------------- /templates/revealing-text.sif: -------------------------------------------------------------------------------- 1 | 2 | 3 | Revealing Text template for Keyframe import in SynfigStudio 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | before&start 19 | end&after 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 0.0000000000 33 | 0.0000000000 34 | 35 | 36 | 37 | 38 | 39 | 40 | 0.0000000000 41 | 0.0000000000 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 1.0000000000 53 | 1.0000000000 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | this text reveals progressively 72 | 73 | 74 | 75 | 0.074763 76 | 0.000000 77 | 0.094494 78 | 1.000000 79 | 80 | 81 | 82 | Sans Serif 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 0.2500000000 99 | 0.2500000000 100 | 101 | 102 | 103 | 104 | 0.5000000000 105 | 0.5000000000 106 | 107 | 108 | 109 | 110 | 0.0000000000 111 | 0.0000000000 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 0.0000000000 137 | 0.0000000000 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 4.0000000000 147 | 0.0000000000 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 1.0000000000 171 | 1.0000000000 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 0.0666666701 191 | 0.0000000000 192 | 193 | 194 | 195 | 196 | -0.0666666701 197 | 0.0000000000 198 | 199 | 200 | 201 | 202 | 203 | 1.000000 204 | 1.000000 205 | 1.000000 206 | 1.000000 207 | 208 | 209 | 0.041083 210 | 0.000000 211 | 0.000000 212 | 0.000000 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 247 | 248 | 249 | 250 | 271 | 272 | -------------------------------------------------------------------------------- /synfig-import-labels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2014-2016 by Berteh 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | #------------------------------------------------ 10 | # 11 | # usage 12 | # 13 | # from synfigstudio (open the project you want to import to) 14 | # caret/right-click > Plug-Ins > Import Labels and Timings 15 | # 16 | # from command line 17 | # python synfig-import-labels.py in.sifz (labels.txt (out.sifz)) 18 | # 19 | # 20 | #------------------------------------------------ 21 | # installation: see http://wiki.synfig.org/wiki/Doc:Plugins 22 | # 23 | #------------------------------------------------ 24 | # Feel free to improve the code below and share your modifications at 25 | # https://github.com/berteh/synfig-import-labels/issues 26 | 27 | import os 28 | import sys 29 | import xml.etree.ElementTree as ET 30 | from xml.sax.saxutils import quoteattr 31 | import re 32 | import settings as s 33 | import random 34 | import csv 35 | import gzip 36 | #import pystache # imported below if s.GENERATE_OBJECTS is True. 37 | 38 | # fix for Synfig seemingly not addding plugin dir to import path 39 | script_dir = os.path.dirname(os.path.realpath(__file__)) 40 | if script_dir not in sys.path: 41 | sys.path.insert(0, script_dir) 42 | 43 | 44 | def frames2sec(fps, secFrame): # convert Synfig "Wh Xm Ys Zf" time notation to in seconds. 45 | pattern = "^(?:(\d+)h)?\s?(?:(\d+)m)?\s?(?:(\d+)s)?\s?(?:(\d+)f)?$" 46 | match = re.match(pattern, secFrame) 47 | [h, m, s, f] = match.groups(0) 48 | h2s = float(h)*3600 if h is not None else 0 49 | m2s = float(m)*60 if m is not None else 0 50 | s2s = float(s) if s is not None else 0 51 | f2s = float(f)/fps if f is not None else 0 52 | return h2s + m2s + s2s + f2s 53 | 54 | def sec2Frames(fps, seconds): # convert seconds to [seconds, seconds, frames] 55 | ss = int(seconds) 56 | sf = int(fps * (seconds - ss)) 57 | return [seconds, ss, sf] 58 | 59 | def getCsvData(csv_filepath): 60 | # Read CSV file and return 2-dimensional list containing the data 61 | header = s.HEADER 62 | data = [] 63 | reader = csv.reader(open(csv_filepath), delimiter=s.TSV_SEPARATOR) 64 | for row in reader: 65 | rowlist = [] 66 | for col in row: 67 | rowlist.append(col) 68 | if(rowlist[0]==s.HEADER[0]): 69 | header = rowlist 70 | else: 71 | data.append(rowlist) 72 | return [header, data] 73 | 74 | def process(sifin_filename, labels_filepath, sifout_filename): 75 | #read audacity labels file 76 | labels_filepath = os.path.join(script_dir,labels_filepath) 77 | if not os.path.exists(labels_filepath): 78 | sys.exit("Labels file not found: %s " % labels_filepath) 79 | with open(labels_filepath, 'r') as f: 80 | print(" reading labels from %s" % labels_filepath) # commented for "print" statements lead to syntax error in synfig. 81 | [header,data] = getCsvData(labels_filepath) 82 | if(len(data) < 1): 83 | sys.exit("Data file %s contains no data or could not be parsed, nothing to generate. Halting."%(labels_filepath)) 84 | 85 | # read input sif(z) file as xml 86 | if(sifin_filename[-4:] == "sifz"): 87 | sifin = gzip.GzipFile(sifin_filename) 88 | else: 89 | sifin = sifin_filename 90 | tree = ET.parse(sifin) 91 | canvas = tree.getroot() 92 | fps = int(float(canvas.get("fps"))) 93 | 94 | # put existing keyframes description in attributes, to enable fast xpath search with no other external module 95 | for key in canvas.findall("./keyframe"): 96 | if key.text is not None : 97 | key.set("desc",quoteattr(key.text)) 98 | # print " copied descriptions of keyframes to attributes in sif file:" 99 | # ET.dump(canvas) 100 | 101 | #process labels into keyframes & objects 102 | template = s.TEMPLATE_NAME 103 | texts = [] 104 | starts = [] 105 | ends = [] 106 | templates = [] 107 | 108 | for line in data: 109 | #convert audacity time to synfig seconds+frame. 110 | [start,ss,sf] = sec2Frames(fps, float(line[0].replace(",","."))) 111 | [end, es, ef] = sec2Frames(fps, float(line[1].replace(",","."))) 112 | desc = str(line[2]) 113 | if ((len(line)>3) and (line[3] != "")): 114 | template = line[3] 115 | 116 | #what follows is reusable for any format, TODO turn into function 117 | if s.IMPORT_START: 118 | k = canvas.find("keyframe[@desc=%s]" % (quoteattr(desc+s.START_SUFFIX))) 119 | if s.OVERWRITE_KEYFRAMES_WITH_SAME_NAME or (k is None): 120 | if (k is None): 121 | #if s.DEBUG: print " creating keyframe: %s" % desc 122 | k = ET.Element('keyframe',{"active": "true"}) 123 | k.text = desc+s.START_SUFFIX 124 | canvas.append(k) 125 | 126 | k.set("time", "%ds %df" % (ss, sf)) #length is set automatically by synfig 127 | #elif s.DEBUG: print " skipping existing start keyframe: %s" % desc 128 | 129 | if s.IMPORT_END and not (start == end): 130 | k = canvas.find("keyframe[@desc=%s]" % (quoteattr(desc+s.END_SUFFIX))) 131 | if s.OVERWRITE_KEYFRAMES_WITH_SAME_NAME or (k is None): 132 | if (k is None): 133 | #if s.DEBUG: print " creating keyframe: %s" % desc 134 | k = ET.Element('keyframe',{"active": "true"}) 135 | k.text = desc+s.END_SUFFIX 136 | canvas.append(k) 137 | 138 | k.set("time", "%ds %df" % (es, ef)) 139 | #elif s.DEBUG: print " skipping existing end keyframe: %s" % desc 140 | 141 | if s.GENERATE_OBJECTS: 142 | texts.append(desc) 143 | starts.append(start) 144 | ends.append(end) 145 | templates.append(template) 146 | 147 | # generate objects 148 | if s.GENERATE_OBJECTS: 149 | print("generating objects") 150 | try: 151 | import pystache 152 | renderer = pystache.Renderer(search_dirs=[os.path.join(script_dir,"templates"),s.TEMPLATE_DIR], file_extension="xml") #todo preferably parse template only once before loop. 153 | except ImportError: 154 | print("Could not load template engine for objects generation.\nPlease verify you have both a pystache and templates subdirectories in %s, or re-download & re-install this plugin."%script_dir) 155 | sys.exit() # skip objects generation 156 | 157 | b = frames2sec(fps, canvas.get("begin-time")) # lower bound for time 158 | e = frames2sec(fps, canvas.get("end-time")) # upper bound for time 159 | d = s.ANIMATION_INTERVAL 160 | r = s.RANDOM_ORIGIN 161 | z = 999934569.1341 # basis for groupid generation, any float > 0 would do. 162 | view = canvas.get("view-box").split() 163 | [minx, maxy, maxx, miny] = [float(elem) for elem in view] 164 | 165 | for i,t in enumerate(texts): 166 | 167 | if s.SPLIT_WORDS: 168 | objects = t.split() # opt add separator as argument, space by default. 169 | objC = len(objects) 170 | objTimes = [starts[i] + (ends[i]-starts[i]) * j for j,word in enumerate(objects)] 171 | else: 172 | objects = [t] 173 | objTimes = [starts[i]] 174 | 175 | #generate object, turn into function 176 | for j,o in enumerate(objects): 177 | if (r>0): 178 | ox = random.uniform(minx, maxx)*r/100 179 | oy = random.uniform(miny, maxy)*r/100 180 | else: 181 | ox = 0 182 | oy = 0 183 | 184 | t1 = max(b,starts[i]-d) 185 | t2 = objTimes[j] 186 | t3 = ends[i] 187 | t4 = min(ends[i]+d,e) 188 | 189 | values = { 190 | 'text':o, 191 | 'value_before':s.VALUE_BEFORE, 192 | 'value_after':s.VALUE_AFTER, 193 | 'loop_before':s.LOOP_BEFORE, 194 | 'loop_middle':s.LOOP_MIDDLE, 195 | 'loop_after':s.LOOP_AFTER, 196 | 'time1':str(t1)+'s', 197 | 'time2':str(t2)+'s', 198 | 'time3':str(t3)+'s', 199 | 'time4':str(t4)+'s', 200 | 'transition':s.WAYPOINT_TYPE, 201 | 'origin_x':ox, 202 | 'origin_y':oy 203 | } 204 | values.update({ 205 | 'group1':(z+t1).hex()[4:-4], 206 | 'group2':(z+t2).hex()[4:-4], 207 | 'group3':(z+t3).hex()[4:-4], 208 | 'group4':(z+t4).hex()[4:-4] 209 | }) 210 | 211 | #print "1 object is being added to canvas for '%s'"%t 212 | #TODO: give feedback to user in GUI if template not found on TemplateNotFoundError 213 | try: 214 | l = renderer.render_name(templates[i], values) 215 | except pystache.common.TemplateNotFoundError: 216 | sys.exit("Could not load template %s for objects generation. Please verify your 'TEMPLATE_DIR' in settings.py."%templates[i]) 217 | 218 | layer = ET.fromstring(l) 219 | canvas.append(layer) 220 | print("added object '%s'"%o) 221 | 222 | # write modified xml tree 223 | if(sifout_filename[-4:] == "sifz"): 224 | sifout_filename = gzip.GzipFile(sifout_filename,'wb') 225 | tree.write(sifout_filename, encoding="UTF-8", xml_declaration=True) 226 | 227 | #main 228 | if len(sys.argv) < 2: 229 | sys.exit("Missing synfig filename as argument") 230 | else: 231 | # defaults 232 | sifin_filename = sys.argv[1] 233 | labels_filepath = sys.argv[2] if len(sys.argv)>2 else os.path.join(os.path.dirname(sifin_filename), s.LABELS_FILE) 234 | sifout_filename = sys.argv[3] if len(sys.argv)>3 else sifin_filename 235 | process(sifin_filename, labels_filepath, sifout_filename) -------------------------------------------------------------------------------- /pystache/README.md: -------------------------------------------------------------------------------- 1 | Pystache 2 | ======== 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ![](http://defunkt.github.com/pystache/images/logo_phillips.png "mustachioed, monocled snake by David Phillips") 14 | 15 | ![](https://secure.travis-ci.org/defunkt/pystache.png "Travis CI current build status") 16 | 17 | [Pystache](http://defunkt.github.com/pystache) is a Python 18 | implementation of [Mustache](http://mustache.github.com/). Mustache is a 19 | framework-agnostic, logic-free templating system inspired by 20 | [ctemplate](http://code.google.com/p/google-ctemplate/) and 21 | [et](http://www.ivan.fomichev.name/2008/05/erlang-template-engine-prototype.html). 22 | Like ctemplate, Mustache "emphasizes separating logic from presentation: 23 | it is impossible to embed application logic in this template language." 24 | 25 | The [mustache(5)](http://mustache.github.com/mustache.5.html) man page 26 | provides a good introduction to Mustache's syntax. For a more complete 27 | (and more current) description of Mustache's behavior, see the official 28 | [Mustache spec](https://github.com/mustache/spec). 29 | 30 | Pystache is [semantically versioned](http://semver.org) and can be found 31 | on [PyPI](http://pypi.python.org/pypi/pystache). This version of 32 | Pystache passes all tests in [version 33 | 1.1.2](https://github.com/mustache/spec/tree/v1.1.2) of the spec. 34 | 35 | 36 | Requirements 37 | ------------ 38 | 39 | Pystache is tested with-- 40 | 41 | - Python 2.4 (requires simplejson [version 42 | 2.0.9](http://pypi.python.org/pypi/simplejson/2.0.9) or earlier) 43 | - Python 2.5 (requires 44 | [simplejson](http://pypi.python.org/pypi/simplejson/)) 45 | - Python 2.6 46 | - Python 2.7 47 | - Python 3.1 48 | - Python 3.2 49 | - Python 3.3 50 | - [PyPy](http://pypy.org/) 51 | 52 | [Distribute](http://packages.python.org/distribute/) (the setuptools fork) 53 | is recommended over [setuptools](http://pypi.python.org/pypi/setuptools), 54 | and is required in some cases (e.g. for Python 3 support). 55 | If you use [pip](http://www.pip-installer.org/), you probably already satisfy 56 | this requirement. 57 | 58 | JSON support is needed only for the command-line interface and to run 59 | the spec tests. We require simplejson for earlier versions of Python 60 | since Python's [json](http://docs.python.org/library/json.html) module 61 | was added in Python 2.6. 62 | 63 | For Python 2.4 we require an earlier version of simplejson since 64 | simplejson stopped officially supporting Python 2.4 in simplejson 65 | version 2.1.0. Earlier versions of simplejson can be installed manually, 66 | as follows: 67 | 68 | pip install 'simplejson<2.1.0' 69 | 70 | Official support for Python 2.4 will end with Pystache version 0.6.0. 71 | 72 | Install It 73 | ---------- 74 | 75 | pip install pystache 76 | 77 | And test it-- 78 | 79 | pystache-test 80 | 81 | To install and test from source (e.g. from GitHub), see the Develop 82 | section. 83 | 84 | Use It 85 | ------ 86 | 87 | >>> import pystache 88 | >>> print pystache.render('Hi {{person}}!', {'person': 'Mom'}) 89 | Hi Mom! 90 | 91 | You can also create dedicated view classes to hold your view logic. 92 | 93 | Here's your view class (in .../examples/readme.py): 94 | 95 | class SayHello(object): 96 | def to(self): 97 | return "Pizza" 98 | 99 | Instantiating like so: 100 | 101 | >>> from pystache.tests.examples.readme import SayHello 102 | >>> hello = SayHello() 103 | 104 | Then your template, say\_hello.mustache (by default in the same 105 | directory as your class definition): 106 | 107 | Hello, {{to}}! 108 | 109 | Pull it together: 110 | 111 | >>> renderer = pystache.Renderer() 112 | >>> print renderer.render(hello) 113 | Hello, Pizza! 114 | 115 | For greater control over rendering (e.g. to specify a custom template 116 | directory), use the `Renderer` class like above. One can pass attributes 117 | to the Renderer class constructor or set them on a Renderer instance. To 118 | customize template loading on a per-view basis, subclass `TemplateSpec`. 119 | See the docstrings of the 120 | [Renderer](https://github.com/defunkt/pystache/blob/master/pystache/renderer.py) 121 | class and 122 | [TemplateSpec](https://github.com/defunkt/pystache/blob/master/pystache/template_spec.py) 123 | class for more information. 124 | 125 | You can also pre-parse a template: 126 | 127 | >>> parsed = pystache.parse(u"Hey {{#who}}{{.}}!{{/who}}") 128 | >>> print parsed 129 | [u'Hey ', _SectionNode(key=u'who', index_begin=12, index_end=18, parsed=[_EscapeNode(key=u'.'), u'!'])] 130 | 131 | And then: 132 | 133 | >>> print renderer.render(parsed, {'who': 'Pops'}) 134 | Hey Pops! 135 | >>> print renderer.render(parsed, {'who': 'you'}) 136 | Hey you! 137 | 138 | Python 3 139 | -------- 140 | 141 | Pystache has supported Python 3 since version 0.5.1. Pystache behaves 142 | slightly differently between Python 2 and 3, as follows: 143 | 144 | - In Python 2, the default html-escape function `cgi.escape()` does 145 | not escape single quotes. In Python 3, the default escape function 146 | `html.escape()` does escape single quotes. 147 | - In both Python 2 and 3, the string and file encodings default to 148 | `sys.getdefaultencoding()`. However, this function can return 149 | different values under Python 2 and 3, even when run from the same 150 | system. Check your own system for the behavior on your system, or do 151 | not rely on the defaults by passing in the encodings explicitly 152 | (e.g. to the `Renderer` class). 153 | 154 | Unicode 155 | ------- 156 | 157 | This section describes how Pystache handles unicode, strings, and 158 | encodings. 159 | 160 | Internally, Pystache uses [only unicode 161 | strings](http://docs.python.org/howto/unicode.html#tips-for-writing-unicode-aware-programs) 162 | (`str` in Python 3 and `unicode` in Python 2). For input, Pystache 163 | accepts both unicode strings and byte strings (`bytes` in Python 3 and 164 | `str` in Python 2). For output, Pystache's template rendering methods 165 | return only unicode. 166 | 167 | Pystache's `Renderer` class supports a number of attributes to control 168 | how Pystache converts byte strings to unicode on input. These include 169 | the `file_encoding`, `string_encoding`, and `decode_errors` attributes. 170 | 171 | The `file_encoding` attribute is the encoding the renderer uses to 172 | convert to unicode any files read from the file system. Similarly, 173 | `string_encoding` is the encoding the renderer uses to convert any other 174 | byte strings encountered during the rendering process into unicode (e.g. 175 | context values that are encoded byte strings). 176 | 177 | The `decode_errors` attribute is what the renderer passes as the 178 | `errors` argument to Python's built-in unicode-decoding function 179 | (`str()` in Python 3 and `unicode()` in Python 2). The valid values for 180 | this argument are `strict`, `ignore`, and `replace`. 181 | 182 | Each of these attributes can be set via the `Renderer` class's 183 | constructor using a keyword argument of the same name. See the Renderer 184 | class's docstrings for further details. In addition, the `file_encoding` 185 | attribute can be controlled on a per-view basis by subclassing the 186 | `TemplateSpec` class. When not specified explicitly, these attributes 187 | default to values set in Pystache's `defaults` module. 188 | 189 | Develop 190 | ------- 191 | 192 | To test from a source distribution (without installing)-- 193 | 194 | python test_pystache.py 195 | 196 | To test Pystache with multiple versions of Python (with a single 197 | command!), you can use [tox](http://pypi.python.org/pypi/tox): 198 | 199 | pip install 'virtualenv<1.8' # Version 1.8 dropped support for Python 2.4. 200 | pip install 'tox<1.4' # Version 1.4 dropped support for Python 2.4. 201 | tox 202 | 203 | If you do not have all Python versions listed in `tox.ini`-- 204 | 205 | tox -e py26,py32 # for example 206 | 207 | The source distribution tests also include doctests and tests from the 208 | Mustache spec. To include tests from the Mustache spec in your test 209 | runs: 210 | 211 | git submodule init 212 | git submodule update 213 | 214 | The test harness parses the spec's (more human-readable) yaml files if 215 | [PyYAML](http://pypi.python.org/pypi/PyYAML) is present. Otherwise, it 216 | parses the json files. To install PyYAML-- 217 | 218 | pip install pyyaml 219 | 220 | To run a subset of the tests, you can use 221 | [nose](http://somethingaboutorange.com/mrl/projects/nose/0.11.1/testing.html): 222 | 223 | pip install nose 224 | nosetests --tests pystache/tests/test_context.py:GetValueTests.test_dictionary__key_present 225 | 226 | ### Using Python 3 with Pystache from source 227 | 228 | Pystache is written in Python 2 and must be converted to Python 3 prior to 229 | using it with Python 3. The installation process (and tox) do this 230 | automatically. 231 | 232 | To convert the code to Python 3 manually (while using Python 3)-- 233 | 234 | python setup.py build 235 | 236 | This writes the converted code to a subdirectory called `build`. 237 | By design, Python 3 builds 238 | [cannot](https://bitbucket.org/tarek/distribute/issue/292/allow-use_2to3-with-python-2) 239 | be created from Python 2. 240 | 241 | To convert the code without using setup.py, you can use 242 | [2to3](http://docs.python.org/library/2to3.html) as follows (two steps)-- 243 | 244 | 2to3 --write --nobackups --no-diffs --doctests_only pystache 245 | 2to3 --write --nobackups --no-diffs pystache 246 | 247 | This converts the code (and doctests) in place. 248 | 249 | To `import pystache` from a source distribution while using Python 3, be 250 | sure that you are importing from a directory containing a converted 251 | version of the code (e.g. from the `build` directory after converting), 252 | and not from the original (unconverted) source directory. Otherwise, you will 253 | get a syntax error. You can help prevent this by not running the Python 254 | IDE from the project directory when importing Pystache while using Python 3. 255 | 256 | 257 | Mailing List 258 | ------------ 259 | 260 | There is a [mailing list](http://librelist.com/browser/pystache/). Note 261 | that there is a bit of a delay between posting a message and seeing it 262 | appear in the mailing list archive. 263 | 264 | Credits 265 | ------- 266 | 267 | >>> context = { 'author': 'Chris Wanstrath', 'maintainer': 'Chris Jerdonek' } 268 | >>> print pystache.render("Author: {{author}}\nMaintainer: {{maintainer}}", context) 269 | Author: Chris Wanstrath 270 | Maintainer: Chris Jerdonek 271 | 272 | Pystache logo by [David Phillips](http://davidphillips.us/) is licensed 273 | under a [Creative Commons Attribution-ShareAlike 3.0 Unported 274 | License](http://creativecommons.org/licenses/by-sa/3.0/deed.en_US). 275 | ![](http://i.creativecommons.org/l/by-sa/3.0/88x31.png "Creative 276 | Commons Attribution-ShareAlike 3.0 Unported License") 277 | -------------------------------------------------------------------------------- /pystache/context.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | Exposes a ContextStack class. 5 | 6 | The Mustache spec makes a special distinction between two types of context 7 | stack elements: hashes and objects. For the purposes of interpreting the 8 | spec, we define these categories mutually exclusively as follows: 9 | 10 | (1) Hash: an item whose type is a subclass of dict. 11 | 12 | (2) Object: an item that is neither a hash nor an instance of a 13 | built-in type. 14 | 15 | """ 16 | 17 | from pystache.common import PystacheError 18 | import collections 19 | 20 | 21 | # This equals '__builtin__' in Python 2 and 'builtins' in Python 3. 22 | _BUILTIN_MODULE = type(0).__module__ 23 | 24 | 25 | # We use this private global variable as a return value to represent a key 26 | # not being found on lookup. This lets us distinguish between the case 27 | # of a key's value being None with the case of a key not being found -- 28 | # without having to rely on exceptions (e.g. KeyError) for flow control. 29 | # 30 | # TODO: eliminate the need for a private global variable, e.g. by using the 31 | # preferred Python approach of "easier to ask for forgiveness than permission": 32 | # http://docs.python.org/glossary.html#term-eafp 33 | class NotFound(object): 34 | pass 35 | _NOT_FOUND = NotFound() 36 | 37 | 38 | def _get_value(context, key): 39 | """ 40 | Retrieve a key's value from a context item. 41 | 42 | Returns _NOT_FOUND if the key does not exist. 43 | 44 | The ContextStack.get() docstring documents this function's intended behavior. 45 | 46 | """ 47 | if isinstance(context, dict): 48 | # Then we consider the argument a "hash" for the purposes of the spec. 49 | # 50 | # We do a membership test to avoid using exceptions for flow control 51 | # (e.g. catching KeyError). 52 | if key in context: 53 | return context[key] 54 | elif type(context).__module__ != _BUILTIN_MODULE: 55 | # Then we consider the argument an "object" for the purposes of 56 | # the spec. 57 | # 58 | # The elif test above lets us avoid treating instances of built-in 59 | # types like integers and strings as objects (cf. issue #81). 60 | # Instances of user-defined classes on the other hand, for example, 61 | # are considered objects by the test above. 62 | try: 63 | attr = getattr(context, key) 64 | except AttributeError: 65 | # TODO: distinguish the case of the attribute not existing from 66 | # an AttributeError being raised by the call to the attribute. 67 | # See the following issue for implementation ideas: 68 | # http://bugs.python.org/issue7559 69 | pass 70 | else: 71 | # TODO: consider using EAFP here instead. 72 | # http://docs.python.org/glossary.html#term-eafp 73 | if isinstance(attr, collections.Callable): 74 | return attr() 75 | return attr 76 | 77 | return _NOT_FOUND 78 | 79 | 80 | class KeyNotFoundError(PystacheError): 81 | 82 | """ 83 | An exception raised when a key is not found in a context stack. 84 | 85 | """ 86 | 87 | def __init__(self, key, details): 88 | self.key = key 89 | self.details = details 90 | 91 | def __str__(self): 92 | return "Key %s not found: %s" % (repr(self.key), self.details) 93 | 94 | 95 | class ContextStack(object): 96 | 97 | """ 98 | Provides dictionary-like access to a stack of zero or more items. 99 | 100 | Instances of this class are meant to act as the rendering context 101 | when rendering Mustache templates in accordance with mustache(5) 102 | and the Mustache spec. 103 | 104 | Instances encapsulate a private stack of hashes, objects, and built-in 105 | type instances. Querying the stack for the value of a key queries 106 | the items in the stack in order from last-added objects to first 107 | (last in, first out). 108 | 109 | Caution: this class does not currently support recursive nesting in 110 | that items in the stack cannot themselves be ContextStack instances. 111 | 112 | See the docstrings of the methods of this class for more details. 113 | 114 | """ 115 | 116 | # We reserve keyword arguments for future options (e.g. a "strict=True" 117 | # option for enabling a strict mode). 118 | def __init__(self, *items): 119 | """ 120 | Construct an instance, and initialize the private stack. 121 | 122 | The *items arguments are the items with which to populate the 123 | initial stack. Items in the argument list are added to the 124 | stack in order so that, in particular, items at the end of 125 | the argument list are queried first when querying the stack. 126 | 127 | Caution: items should not themselves be ContextStack instances, as 128 | recursive nesting does not behave as one might expect. 129 | 130 | """ 131 | self._stack = list(items) 132 | 133 | def __repr__(self): 134 | """ 135 | Return a string representation of the instance. 136 | 137 | For example-- 138 | 139 | >>> context = ContextStack({'alpha': 'abc'}, {'numeric': 123}) 140 | >>> repr(context) 141 | "ContextStack({'alpha': 'abc'}, {'numeric': 123})" 142 | 143 | """ 144 | return "%s%s" % (self.__class__.__name__, tuple(self._stack)) 145 | 146 | @staticmethod 147 | def create(*context, **kwargs): 148 | """ 149 | Build a ContextStack instance from a sequence of context-like items. 150 | 151 | This factory-style method is more general than the ContextStack class's 152 | constructor in that, unlike the constructor, the argument list 153 | can itself contain ContextStack instances. 154 | 155 | Here is an example illustrating various aspects of this method: 156 | 157 | >>> obj1 = {'animal': 'cat', 'vegetable': 'carrot', 'mineral': 'copper'} 158 | >>> obj2 = ContextStack({'vegetable': 'spinach', 'mineral': 'silver'}) 159 | >>> 160 | >>> context = ContextStack.create(obj1, None, obj2, mineral='gold') 161 | >>> 162 | >>> context.get('animal') 163 | 'cat' 164 | >>> context.get('vegetable') 165 | 'spinach' 166 | >>> context.get('mineral') 167 | 'gold' 168 | 169 | Arguments: 170 | 171 | *context: zero or more dictionaries, ContextStack instances, or objects 172 | with which to populate the initial context stack. None 173 | arguments will be skipped. Items in the *context list are 174 | added to the stack in order so that later items in the argument 175 | list take precedence over earlier items. This behavior is the 176 | same as the constructor's. 177 | 178 | **kwargs: additional key-value data to add to the context stack. 179 | As these arguments appear after all items in the *context list, 180 | in the case of key conflicts these values take precedence over 181 | all items in the *context list. This behavior is the same as 182 | the constructor's. 183 | 184 | """ 185 | items = context 186 | 187 | context = ContextStack() 188 | 189 | for item in items: 190 | if item is None: 191 | continue 192 | if isinstance(item, ContextStack): 193 | context._stack.extend(item._stack) 194 | else: 195 | context.push(item) 196 | 197 | if kwargs: 198 | context.push(kwargs) 199 | 200 | return context 201 | 202 | # TODO: add more unit tests for this. 203 | # TODO: update the docstring for dotted names. 204 | def get(self, name): 205 | """ 206 | Resolve a dotted name against the current context stack. 207 | 208 | This function follows the rules outlined in the section of the 209 | spec regarding tag interpolation. This function returns the value 210 | as is and does not coerce the return value to a string. 211 | 212 | Arguments: 213 | 214 | name: a dotted or non-dotted name. 215 | 216 | default: the value to return if name resolution fails at any point. 217 | Defaults to the empty string per the Mustache spec. 218 | 219 | This method queries items in the stack in order from last-added 220 | objects to first (last in, first out). The value returned is 221 | the value of the key in the first item that contains the key. 222 | If the key is not found in any item in the stack, then the default 223 | value is returned. The default value defaults to None. 224 | 225 | In accordance with the spec, this method queries items in the 226 | stack for a key differently depending on whether the item is a 227 | hash, object, or neither (as defined in the module docstring): 228 | 229 | (1) Hash: if the item is a hash, then the key's value is the 230 | dictionary value of the key. If the dictionary doesn't contain 231 | the key, then the key is considered not found. 232 | 233 | (2) Object: if the item is an an object, then the method looks for 234 | an attribute with the same name as the key. If an attribute 235 | with that name exists, the value of the attribute is returned. 236 | If the attribute is callable, however (i.e. if the attribute 237 | is a method), then the attribute is called with no arguments 238 | and that value is returned. If there is no attribute with 239 | the same name as the key, then the key is considered not found. 240 | 241 | (3) Neither: if the item is neither a hash nor an object, then 242 | the key is considered not found. 243 | 244 | *Caution*: 245 | 246 | Callables are handled differently depending on whether they are 247 | dictionary values, as in (1) above, or attributes, as in (2). 248 | The former are returned as-is, while the latter are first 249 | called and that value returned. 250 | 251 | Here is an example to illustrate: 252 | 253 | >>> def greet(): 254 | ... return "Hi Bob!" 255 | >>> 256 | >>> class Greeter(object): 257 | ... greet = None 258 | >>> 259 | >>> dct = {'greet': greet} 260 | >>> obj = Greeter() 261 | >>> obj.greet = greet 262 | >>> 263 | >>> dct['greet'] is obj.greet 264 | True 265 | >>> ContextStack(dct).get('greet') #doctest: +ELLIPSIS 266 | 267 | >>> ContextStack(obj).get('greet') 268 | 'Hi Bob!' 269 | 270 | TODO: explain the rationale for this difference in treatment. 271 | 272 | """ 273 | if name == '.': 274 | try: 275 | return self.top() 276 | except IndexError: 277 | raise KeyNotFoundError(".", "empty context stack") 278 | 279 | parts = name.split('.') 280 | 281 | try: 282 | result = self._get_simple(parts[0]) 283 | except KeyNotFoundError: 284 | raise KeyNotFoundError(name, "first part") 285 | 286 | for part in parts[1:]: 287 | # The full context stack is not used to resolve the remaining parts. 288 | # From the spec-- 289 | # 290 | # 5) If any name parts were retained in step 1, each should be 291 | # resolved against a context stack containing only the result 292 | # from the former resolution. If any part fails resolution, the 293 | # result should be considered falsey, and should interpolate as 294 | # the empty string. 295 | # 296 | # TODO: make sure we have a test case for the above point. 297 | result = _get_value(result, part) 298 | # TODO: consider using EAFP here instead. 299 | # http://docs.python.org/glossary.html#term-eafp 300 | if result is _NOT_FOUND: 301 | raise KeyNotFoundError(name, "missing %s" % repr(part)) 302 | 303 | return result 304 | 305 | def _get_simple(self, name): 306 | """ 307 | Query the stack for a non-dotted name. 308 | 309 | """ 310 | for item in reversed(self._stack): 311 | result = _get_value(item, name) 312 | if result is not _NOT_FOUND: 313 | return result 314 | 315 | raise KeyNotFoundError(name, "part missing") 316 | 317 | def push(self, item): 318 | """ 319 | Push an item onto the stack. 320 | 321 | """ 322 | self._stack.append(item) 323 | 324 | def pop(self): 325 | """ 326 | Pop an item off of the stack, and return it. 327 | 328 | """ 329 | return self._stack.pop() 330 | 331 | def top(self): 332 | """ 333 | Return the item last added to the stack. 334 | 335 | """ 336 | return self._stack[-1] 337 | 338 | def copy(self): 339 | """ 340 | Return a copy of this instance. 341 | 342 | """ 343 | return ContextStack(*self._stack) 344 | -------------------------------------------------------------------------------- /pystache/parser.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | Exposes a parse() function to parse template strings. 5 | 6 | """ 7 | 8 | import re 9 | 10 | from pystache import defaults 11 | from pystache.parsed import ParsedTemplate 12 | import collections 13 | 14 | 15 | END_OF_LINE_CHARACTERS = ['\r', '\n'] 16 | NON_BLANK_RE = re.compile(r'^(.)', re.M) 17 | 18 | 19 | # TODO: add some unit tests for this. 20 | # TODO: add a test case that checks for spurious spaces. 21 | # TODO: add test cases for delimiters. 22 | def parse(template, delimiters=None): 23 | """ 24 | Parse a unicode template string and return a ParsedTemplate instance. 25 | 26 | Arguments: 27 | 28 | template: a unicode template string. 29 | 30 | delimiters: a 2-tuple of delimiters. Defaults to the package default. 31 | 32 | Examples: 33 | 34 | >>> parsed = parse("Hey {{#who}}{{name}}!{{/who}}") 35 | >>> print(str(parsed).replace('u', '')) # This is a hack to get the test to pass both in Python 2 and 3. 36 | ['Hey ', _SectionNode(key='who', index_begin=12, index_end=21, parsed=[_EscapeNode(key='name'), '!'])] 37 | 38 | """ 39 | if type(template) is not str: 40 | raise Exception("Template is not unicode: %s" % type(template)) 41 | parser = _Parser(delimiters) 42 | return parser.parse(template) 43 | 44 | 45 | def _compile_template_re(delimiters): 46 | """ 47 | Return a regular expression object (re.RegexObject) instance. 48 | 49 | """ 50 | # The possible tag type characters following the opening tag, 51 | # excluding "=" and "{". 52 | tag_types = "!>&/#^" 53 | 54 | # TODO: are we following this in the spec? 55 | # 56 | # The tag's content MUST be a non-whitespace character sequence 57 | # NOT containing the current closing delimiter. 58 | # 59 | tag = r""" 60 | (?P[\ \t]*) 61 | %(otag)s \s* 62 | (?: 63 | (?P=) \s* (?P.+?) \s* = | 64 | (?P{) \s* (?P.+?) \s* } | 65 | (?P[%(tag_types)s]?) \s* (?P[\s\S]+?) 66 | ) 67 | \s* %(ctag)s 68 | """ % {'tag_types': tag_types, 'otag': re.escape(delimiters[0]), 'ctag': re.escape(delimiters[1])} 69 | 70 | return re.compile(tag, re.VERBOSE) 71 | 72 | 73 | class ParsingError(Exception): 74 | 75 | pass 76 | 77 | 78 | ## Node types 79 | 80 | def _format(obj, exclude=None): 81 | if exclude is None: 82 | exclude = [] 83 | exclude.append('key') 84 | attrs = obj.__dict__ 85 | names = list(set(attrs.keys()) - set(exclude)) 86 | names.sort() 87 | names.insert(0, 'key') 88 | args = ["%s=%s" % (name, repr(attrs[name])) for name in names] 89 | return "%s(%s)" % (obj.__class__.__name__, ", ".join(args)) 90 | 91 | 92 | class _CommentNode(object): 93 | 94 | def __repr__(self): 95 | return _format(self) 96 | 97 | def render(self, engine, context): 98 | return '' 99 | 100 | 101 | class _ChangeNode(object): 102 | 103 | def __init__(self, delimiters): 104 | self.delimiters = delimiters 105 | 106 | def __repr__(self): 107 | return _format(self) 108 | 109 | def render(self, engine, context): 110 | return '' 111 | 112 | 113 | class _EscapeNode(object): 114 | 115 | def __init__(self, key): 116 | self.key = key 117 | 118 | def __repr__(self): 119 | return _format(self) 120 | 121 | def render(self, engine, context): 122 | s = engine.fetch_string(context, self.key) 123 | return engine.escape(s) 124 | 125 | 126 | class _LiteralNode(object): 127 | 128 | def __init__(self, key): 129 | self.key = key 130 | 131 | def __repr__(self): 132 | return _format(self) 133 | 134 | def render(self, engine, context): 135 | s = engine.fetch_string(context, self.key) 136 | return engine.literal(s) 137 | 138 | 139 | class _PartialNode(object): 140 | 141 | def __init__(self, key, indent): 142 | self.key = key 143 | self.indent = indent 144 | 145 | def __repr__(self): 146 | return _format(self) 147 | 148 | def render(self, engine, context): 149 | template = engine.resolve_partial(self.key) 150 | # Indent before rendering. 151 | template = re.sub(NON_BLANK_RE, self.indent + r'\1', template) 152 | 153 | return engine.render(template, context) 154 | 155 | 156 | class _InvertedNode(object): 157 | 158 | def __init__(self, key, parsed_section): 159 | self.key = key 160 | self.parsed_section = parsed_section 161 | 162 | def __repr__(self): 163 | return _format(self) 164 | 165 | def render(self, engine, context): 166 | # TODO: is there a bug because we are not using the same 167 | # logic as in fetch_string()? 168 | data = engine.resolve_context(context, self.key) 169 | # Note that lambdas are considered truthy for inverted sections 170 | # per the spec. 171 | if data: 172 | return '' 173 | return self.parsed_section.render(engine, context) 174 | 175 | 176 | class _SectionNode(object): 177 | 178 | # TODO: the template_ and parsed_template_ arguments don't both seem 179 | # to be necessary. Can we remove one of them? For example, if 180 | # callable(data) is True, then the initial parsed_template isn't used. 181 | def __init__(self, key, parsed, delimiters, template, index_begin, index_end): 182 | self.delimiters = delimiters 183 | self.key = key 184 | self.parsed = parsed 185 | self.template = template 186 | self.index_begin = index_begin 187 | self.index_end = index_end 188 | 189 | def __repr__(self): 190 | return _format(self, exclude=['delimiters', 'template']) 191 | 192 | def render(self, engine, context): 193 | values = engine.fetch_section_data(context, self.key) 194 | 195 | parts = [] 196 | for val in values: 197 | if isinstance(val, collections.Callable): 198 | # Lambdas special case section rendering and bypass pushing 199 | # the data value onto the context stack. From the spec-- 200 | # 201 | # When used as the data value for a Section tag, the 202 | # lambda MUST be treatable as an arity 1 function, and 203 | # invoked as such (passing a String containing the 204 | # unprocessed section contents). The returned value 205 | # MUST be rendered against the current delimiters, then 206 | # interpolated in place of the section. 207 | # 208 | # Also see-- 209 | # 210 | # https://github.com/defunkt/pystache/issues/113 211 | # 212 | # TODO: should we check the arity? 213 | val = val(self.template[self.index_begin:self.index_end]) 214 | val = engine._render_value(val, context, delimiters=self.delimiters) 215 | parts.append(val) 216 | continue 217 | 218 | context.push(val) 219 | parts.append(self.parsed.render(engine, context)) 220 | context.pop() 221 | 222 | return str(''.join(parts)) 223 | 224 | 225 | class _Parser(object): 226 | 227 | _delimiters = None 228 | _template_re = None 229 | 230 | def __init__(self, delimiters=None): 231 | if delimiters is None: 232 | delimiters = defaults.DELIMITERS 233 | 234 | self._delimiters = delimiters 235 | 236 | def _compile_delimiters(self): 237 | self._template_re = _compile_template_re(self._delimiters) 238 | 239 | def _change_delimiters(self, delimiters): 240 | self._delimiters = delimiters 241 | self._compile_delimiters() 242 | 243 | def parse(self, template): 244 | """ 245 | Parse a template string starting at some index. 246 | 247 | This method uses the current tag delimiter. 248 | 249 | Arguments: 250 | 251 | template: a unicode string that is the template to parse. 252 | 253 | index: the index at which to start parsing. 254 | 255 | Returns: 256 | 257 | a ParsedTemplate instance. 258 | 259 | """ 260 | self._compile_delimiters() 261 | 262 | start_index = 0 263 | content_end_index, parsed_section, section_key = None, None, None 264 | parsed_template = ParsedTemplate() 265 | 266 | states = [] 267 | 268 | while True: 269 | match = self._template_re.search(template, start_index) 270 | 271 | if match is None: 272 | break 273 | 274 | match_index = match.start() 275 | end_index = match.end() 276 | 277 | matches = match.groupdict() 278 | 279 | # Normalize the matches dictionary. 280 | if matches['change'] is not None: 281 | matches.update(tag='=', tag_key=matches['delims']) 282 | elif matches['raw'] is not None: 283 | matches.update(tag='&', tag_key=matches['raw_name']) 284 | 285 | tag_type = matches['tag'] 286 | tag_key = matches['tag_key'] 287 | leading_whitespace = matches['whitespace'] 288 | 289 | # Standalone (non-interpolation) tags consume the entire line, 290 | # both leading whitespace and trailing newline. 291 | did_tag_begin_line = match_index == 0 or template[match_index - 1] in END_OF_LINE_CHARACTERS 292 | did_tag_end_line = end_index == len(template) or template[end_index] in END_OF_LINE_CHARACTERS 293 | is_tag_interpolating = tag_type in ['', '&'] 294 | 295 | if did_tag_begin_line and did_tag_end_line and not is_tag_interpolating: 296 | if end_index < len(template): 297 | end_index += template[end_index] == '\r' and 1 or 0 298 | if end_index < len(template): 299 | end_index += template[end_index] == '\n' and 1 or 0 300 | elif leading_whitespace: 301 | match_index += len(leading_whitespace) 302 | leading_whitespace = '' 303 | 304 | # Avoid adding spurious empty strings to the parse tree. 305 | if start_index != match_index: 306 | parsed_template.add(template[start_index:match_index]) 307 | 308 | start_index = end_index 309 | 310 | if tag_type in ('#', '^'): 311 | # Cache current state. 312 | state = (tag_type, end_index, section_key, parsed_template) 313 | states.append(state) 314 | 315 | # Initialize new state 316 | section_key, parsed_template = tag_key, ParsedTemplate() 317 | continue 318 | 319 | if tag_type == '/': 320 | if tag_key != section_key: 321 | raise ParsingError("Section end tag mismatch: %s != %s" % (tag_key, section_key)) 322 | 323 | # Restore previous state with newly found section data. 324 | parsed_section = parsed_template 325 | 326 | (tag_type, section_start_index, section_key, parsed_template) = states.pop() 327 | node = self._make_section_node(template, tag_type, tag_key, parsed_section, 328 | section_start_index, match_index) 329 | 330 | else: 331 | node = self._make_interpolation_node(tag_type, tag_key, leading_whitespace) 332 | 333 | parsed_template.add(node) 334 | 335 | # Avoid adding spurious empty strings to the parse tree. 336 | if start_index != len(template): 337 | parsed_template.add(template[start_index:]) 338 | 339 | return parsed_template 340 | 341 | def _make_interpolation_node(self, tag_type, tag_key, leading_whitespace): 342 | """ 343 | Create and return a non-section node for the parse tree. 344 | 345 | """ 346 | # TODO: switch to using a dictionary instead of a bunch of ifs and elifs. 347 | if tag_type == '!': 348 | return _CommentNode() 349 | 350 | if tag_type == '=': 351 | delimiters = tag_key.split() 352 | self._change_delimiters(delimiters) 353 | return _ChangeNode(delimiters) 354 | 355 | if tag_type == '': 356 | return _EscapeNode(tag_key) 357 | 358 | if tag_type == '&': 359 | return _LiteralNode(tag_key) 360 | 361 | if tag_type == '>': 362 | return _PartialNode(tag_key, leading_whitespace) 363 | 364 | raise Exception("Invalid symbol for interpolation tag: %s" % repr(tag_type)) 365 | 366 | def _make_section_node(self, template, tag_type, tag_key, parsed_section, 367 | section_start_index, section_end_index): 368 | """ 369 | Create and return a section node for the parse tree. 370 | 371 | """ 372 | if tag_type == '#': 373 | return _SectionNode(tag_key, parsed_section, self._delimiters, 374 | template, section_start_index, section_end_index) 375 | 376 | if tag_type == '^': 377 | return _InvertedNode(tag_key, parsed_section) 378 | 379 | raise Exception("Invalid symbol for section tag: %s" % repr(tag_type)) 380 | 381 | -------------------------------------------------------------------------------- /pystache/renderer.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | This module provides a Renderer class to render templates. 5 | 6 | """ 7 | 8 | import sys 9 | 10 | from pystache import defaults 11 | from pystache.common import TemplateNotFoundError, MissingTags, is_string 12 | from pystache.context import ContextStack, KeyNotFoundError 13 | from pystache.loader import Loader 14 | from pystache.parsed import ParsedTemplate 15 | from pystache.renderengine import context_get, RenderEngine 16 | from pystache.specloader import SpecLoader 17 | from pystache.template_spec import TemplateSpec 18 | 19 | 20 | class Renderer(object): 21 | 22 | """ 23 | A class for rendering mustache templates. 24 | 25 | This class supports several rendering options which are described in 26 | the constructor's docstring. Other behavior can be customized by 27 | subclassing this class. 28 | 29 | For example, one can pass a string-string dictionary to the constructor 30 | to bypass loading partials from the file system: 31 | 32 | >>> partials = {'partial': 'Hello, {{thing}}!'} 33 | >>> renderer = Renderer(partials=partials) 34 | >>> # We apply print to make the test work in Python 3 after 2to3. 35 | >>> print(renderer.render('{{>partial}}', {'thing': 'world'})) 36 | Hello, world! 37 | 38 | To customize string coercion (e.g. to render False values as ''), one can 39 | subclass this class. For example: 40 | 41 | class MyRenderer(Renderer): 42 | def str_coerce(self, val): 43 | if not val: 44 | return '' 45 | else: 46 | return str(val) 47 | 48 | """ 49 | 50 | def __init__(self, file_encoding=None, string_encoding=None, 51 | decode_errors=None, search_dirs=None, file_extension=None, 52 | escape=None, partials=None, missing_tags=None): 53 | """ 54 | Construct an instance. 55 | 56 | Arguments: 57 | 58 | file_encoding: the name of the encoding to use by default when 59 | reading template files. All templates are converted to unicode 60 | prior to parsing. Defaults to the package default. 61 | 62 | string_encoding: the name of the encoding to use when converting 63 | to unicode any byte strings (type str in Python 2) encountered 64 | during the rendering process. This name will be passed as the 65 | encoding argument to the built-in function unicode(). 66 | Defaults to the package default. 67 | 68 | decode_errors: the string to pass as the errors argument to the 69 | built-in function unicode() when converting byte strings to 70 | unicode. Defaults to the package default. 71 | 72 | search_dirs: the list of directories in which to search when 73 | loading a template by name or file name. If given a string, 74 | the method interprets the string as a single directory. 75 | Defaults to the package default. 76 | 77 | file_extension: the template file extension. Pass False for no 78 | extension (i.e. to use extensionless template files). 79 | Defaults to the package default. 80 | 81 | partials: an object (e.g. a dictionary) for custom partial loading 82 | during the rendering process. 83 | The object should have a get() method that accepts a string 84 | and returns the corresponding template as a string, preferably 85 | as a unicode string. If there is no template with that name, 86 | the get() method should either return None (as dict.get() does) 87 | or raise an exception. 88 | If this argument is None, the rendering process will use 89 | the normal procedure of locating and reading templates from 90 | the file system -- using relevant instance attributes like 91 | search_dirs, file_encoding, etc. 92 | 93 | escape: the function used to escape variable tag values when 94 | rendering a template. The function should accept a unicode 95 | string (or subclass of unicode) and return an escaped string 96 | that is again unicode (or a subclass of unicode). 97 | This function need not handle strings of type `str` because 98 | this class will only pass it unicode strings. The constructor 99 | assigns this function to the constructed instance's escape() 100 | method. 101 | To disable escaping entirely, one can pass `lambda u: u` 102 | as the escape function, for example. One may also wish to 103 | consider using markupsafe's escape function: markupsafe.escape(). 104 | This argument defaults to the package default. 105 | 106 | missing_tags: a string specifying how to handle missing tags. 107 | If 'strict', an error is raised on a missing tag. If 'ignore', 108 | the value of the tag is the empty string. Defaults to the 109 | package default. 110 | 111 | """ 112 | if decode_errors is None: 113 | decode_errors = defaults.DECODE_ERRORS 114 | 115 | if escape is None: 116 | escape = defaults.TAG_ESCAPE 117 | 118 | if file_encoding is None: 119 | file_encoding = defaults.FILE_ENCODING 120 | 121 | if file_extension is None: 122 | file_extension = defaults.TEMPLATE_EXTENSION 123 | 124 | if missing_tags is None: 125 | missing_tags = defaults.MISSING_TAGS 126 | 127 | if search_dirs is None: 128 | search_dirs = defaults.SEARCH_DIRS 129 | 130 | if string_encoding is None: 131 | string_encoding = defaults.STRING_ENCODING 132 | 133 | if isinstance(search_dirs, str): 134 | search_dirs = [search_dirs] 135 | 136 | self._context = None 137 | self.decode_errors = decode_errors 138 | self.escape = escape 139 | self.file_encoding = file_encoding 140 | self.file_extension = file_extension 141 | self.missing_tags = missing_tags 142 | self.partials = partials 143 | self.search_dirs = search_dirs 144 | self.string_encoding = string_encoding 145 | 146 | # This is an experimental way of giving views access to the current context. 147 | # TODO: consider another approach of not giving access via a property, 148 | # but instead letting the caller pass the initial context to the 149 | # main render() method by reference. This approach would probably 150 | # be less likely to be misused. 151 | @property 152 | def context(self): 153 | """ 154 | Return the current rendering context [experimental]. 155 | 156 | """ 157 | return self._context 158 | 159 | # We could not choose str() as the name because 2to3 renames the unicode() 160 | # method of this class to str(). 161 | def str_coerce(self, val): 162 | """ 163 | Coerce a non-string value to a string. 164 | 165 | This method is called whenever a non-string is encountered during the 166 | rendering process when a string is needed (e.g. if a context value 167 | for string interpolation is not a string). To customize string 168 | coercion, you can override this method. 169 | 170 | """ 171 | return str(val) 172 | 173 | def _to_unicode_soft(self, s): 174 | """ 175 | Convert a basestring to unicode, preserving any unicode subclass. 176 | 177 | """ 178 | # We type-check to avoid "TypeError: decoding Unicode is not supported". 179 | # We avoid the Python ternary operator for Python 2.4 support. 180 | if isinstance(s, str): 181 | return s 182 | return self.str(s) 183 | 184 | def _to_unicode_hard(self, s): 185 | """ 186 | Convert a basestring to a string with type unicode (not subclass). 187 | 188 | """ 189 | return str(self._to_unicode_soft(s)) 190 | 191 | def _escape_to_unicode(self, s): 192 | """ 193 | Convert a basestring to unicode (preserving any unicode subclass), and escape it. 194 | 195 | Returns a unicode string (not subclass). 196 | 197 | """ 198 | return str(self.escape(self._to_unicode_soft(s))) 199 | 200 | def str(self, b, encoding=None): 201 | """ 202 | Convert a byte string to unicode, using string_encoding and decode_errors. 203 | 204 | Arguments: 205 | 206 | b: a byte string. 207 | 208 | encoding: the name of an encoding. Defaults to the string_encoding 209 | attribute for this instance. 210 | 211 | Raises: 212 | 213 | TypeError: Because this method calls Python's built-in unicode() 214 | function, this method raises the following exception if the 215 | given string is already unicode: 216 | 217 | TypeError: decoding Unicode is not supported 218 | 219 | """ 220 | if encoding is None: 221 | encoding = self.string_encoding 222 | 223 | # TODO: Wrap UnicodeDecodeErrors with a message about setting 224 | # the string_encoding and decode_errors attributes. 225 | return str(b, encoding, self.decode_errors) 226 | 227 | def _make_loader(self): 228 | """ 229 | Create a Loader instance using current attributes. 230 | 231 | """ 232 | return Loader(file_encoding=self.file_encoding, extension=self.file_extension, 233 | to_unicode=self.str, search_dirs=self.search_dirs) 234 | 235 | def _make_load_template(self): 236 | """ 237 | Return a function that loads a template by name. 238 | 239 | """ 240 | loader = self._make_loader() 241 | 242 | def load_template(template_name): 243 | return loader.load_name(template_name) 244 | 245 | return load_template 246 | 247 | def _make_load_partial(self): 248 | """ 249 | Return a function that loads a partial by name. 250 | 251 | """ 252 | if self.partials is None: 253 | return self._make_load_template() 254 | 255 | # Otherwise, create a function from the custom partial loader. 256 | partials = self.partials 257 | 258 | def load_partial(name): 259 | # TODO: consider using EAFP here instead. 260 | # http://docs.python.org/glossary.html#term-eafp 261 | # This would mean requiring that the custom partial loader 262 | # raise a KeyError on name not found. 263 | template = partials.get(name) 264 | if template is None: 265 | raise TemplateNotFoundError("Name %s not found in partials: %s" % 266 | (repr(name), type(partials))) 267 | 268 | # RenderEngine requires that the return value be unicode. 269 | return self._to_unicode_hard(template) 270 | 271 | return load_partial 272 | 273 | def _is_missing_tags_strict(self): 274 | """ 275 | Return whether missing_tags is set to strict. 276 | 277 | """ 278 | val = self.missing_tags 279 | 280 | if val == MissingTags.strict: 281 | return True 282 | elif val == MissingTags.ignore: 283 | return False 284 | 285 | raise Exception("Unsupported 'missing_tags' value: %s" % repr(val)) 286 | 287 | def _make_resolve_partial(self): 288 | """ 289 | Return the resolve_partial function to pass to RenderEngine.__init__(). 290 | 291 | """ 292 | load_partial = self._make_load_partial() 293 | 294 | if self._is_missing_tags_strict(): 295 | return load_partial 296 | # Otherwise, ignore missing tags. 297 | 298 | def resolve_partial(name): 299 | try: 300 | return load_partial(name) 301 | except TemplateNotFoundError: 302 | return '' 303 | 304 | return resolve_partial 305 | 306 | def _make_resolve_context(self): 307 | """ 308 | Return the resolve_context function to pass to RenderEngine.__init__(). 309 | 310 | """ 311 | if self._is_missing_tags_strict(): 312 | return context_get 313 | # Otherwise, ignore missing tags. 314 | 315 | def resolve_context(stack, name): 316 | try: 317 | return context_get(stack, name) 318 | except KeyNotFoundError: 319 | return '' 320 | 321 | return resolve_context 322 | 323 | def _make_render_engine(self): 324 | """ 325 | Return a RenderEngine instance for rendering. 326 | 327 | """ 328 | resolve_context = self._make_resolve_context() 329 | resolve_partial = self._make_resolve_partial() 330 | 331 | engine = RenderEngine(literal=self._to_unicode_hard, 332 | escape=self._escape_to_unicode, 333 | resolve_context=resolve_context, 334 | resolve_partial=resolve_partial, 335 | to_str=self.str_coerce) 336 | return engine 337 | 338 | # TODO: add unit tests for this method. 339 | def load_template(self, template_name): 340 | """ 341 | Load a template by name from the file system. 342 | 343 | """ 344 | load_template = self._make_load_template() 345 | return load_template(template_name) 346 | 347 | def _render_object(self, obj, *context, **kwargs): 348 | """ 349 | Render the template associated with the given object. 350 | 351 | """ 352 | loader = self._make_loader() 353 | 354 | # TODO: consider an approach that does not require using an if 355 | # block here. For example, perhaps this class's loader can be 356 | # a SpecLoader in all cases, and the SpecLoader instance can 357 | # check the object's type. Or perhaps Loader and SpecLoader 358 | # can be refactored to implement the same interface. 359 | if isinstance(obj, TemplateSpec): 360 | loader = SpecLoader(loader) 361 | template = loader.load(obj) 362 | else: 363 | template = loader.load_object(obj) 364 | 365 | context = [obj] + list(context) 366 | 367 | return self._render_string(template, *context, **kwargs) 368 | 369 | def render_name(self, template_name, *context, **kwargs): 370 | """ 371 | Render the template with the given name using the given context. 372 | 373 | See the render() docstring for more information. 374 | 375 | """ 376 | loader = self._make_loader() 377 | template = loader.load_name(template_name) 378 | return self._render_string(template, *context, **kwargs) 379 | 380 | def render_path(self, template_path, *context, **kwargs): 381 | """ 382 | Render the template at the given path using the given context. 383 | 384 | Read the render() docstring for more information. 385 | 386 | """ 387 | loader = self._make_loader() 388 | template = loader.read(template_path) 389 | 390 | return self._render_string(template, *context, **kwargs) 391 | 392 | def _render_string(self, template, *context, **kwargs): 393 | """ 394 | Render the given template string using the given context. 395 | 396 | """ 397 | # RenderEngine.render() requires that the template string be unicode. 398 | template = self._to_unicode_hard(template) 399 | 400 | render_func = lambda engine, stack: engine.render(template, stack) 401 | 402 | return self._render_final(render_func, *context, **kwargs) 403 | 404 | # All calls to render() should end here because it prepares the 405 | # context stack correctly. 406 | def _render_final(self, render_func, *context, **kwargs): 407 | """ 408 | Arguments: 409 | 410 | render_func: a function that accepts a RenderEngine and ContextStack 411 | instance and returns a template rendering as a unicode string. 412 | 413 | """ 414 | stack = ContextStack.create(*context, **kwargs) 415 | self._context = stack 416 | 417 | engine = self._make_render_engine() 418 | 419 | return render_func(engine, stack) 420 | 421 | def render(self, template, *context, **kwargs): 422 | """ 423 | Render the given template string, view template, or parsed template. 424 | 425 | Returns a unicode string. 426 | 427 | Prior to rendering, this method will convert a template that is a 428 | byte string (type str in Python 2) to unicode using the string_encoding 429 | and decode_errors attributes. See the constructor docstring for 430 | more information. 431 | 432 | Arguments: 433 | 434 | template: a template string that is unicode or a byte string, 435 | a ParsedTemplate instance, or another object instance. In the 436 | final case, the function first looks for the template associated 437 | to the object by calling this class's get_associated_template() 438 | method. The rendering process also uses the passed object as 439 | the first element of the context stack when rendering. 440 | 441 | *context: zero or more dictionaries, ContextStack instances, or objects 442 | with which to populate the initial context stack. None 443 | arguments are skipped. Items in the *context list are added to 444 | the context stack in order so that later items in the argument 445 | list take precedence over earlier items. 446 | 447 | **kwargs: additional key-value data to add to the context stack. 448 | As these arguments appear after all items in the *context list, 449 | in the case of key conflicts these values take precedence over 450 | all items in the *context list. 451 | 452 | """ 453 | if is_string(template): 454 | return self._render_string(template, *context, **kwargs) 455 | if isinstance(template, ParsedTemplate): 456 | render_func = lambda engine, stack: template.render(engine, stack) 457 | return self._render_final(render_func, *context, **kwargs) 458 | # Otherwise, we assume the template is an object. 459 | 460 | return self._render_object(template, *context, **kwargs) 461 | 462 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /test/appearing-text_result.sif: -------------------------------------------------------------------------------- 1 | 2 | 3 | Synfig Test for Keyframe import 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | part one 19 | part one - end 20 | part two 21 | part two - end 22 | part three 23 | part three - end 24 | part four 25 | part four - end 26 | part five 27 | part five - end 28 | part six 29 | part six - end 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 0.0000000000 43 | 0.0000000000 44 | 45 | 46 | 47 | 48 | 49 | 50 | 0.0000000000 51 | 0.0000000000 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 1.0000000000 63 | 1.0000000000 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 1.000000 83 | 1.000000 84 | 1.000000 85 | 1.000000 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | this animation has been generated automatically 101 | from an Audacity Labels file, with SynfigStudio. 102 | All Open Source animation 103 | 104 | 105 | 106 | 0.000001 107 | 0.000001 108 | 0.000001 109 | 1.000000 110 | 111 | 112 | 113 | Sans Serif 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 0.1333333403 130 | 0.1333333403 131 | 132 | 133 | 134 | 135 | 0.5000000000 136 | 0.5000000000 137 | 138 | 139 | 140 | 141 | 0.9415920973 142 | -1.7408642769 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | part one 201 | 202 | 203 | 204 | 0.300000 205 | 0.000000 206 | 0.000000 207 | 1.000000 208 | 209 | 210 | 211 | Sans Serif 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 0.2500000000 228 | 0.2500000000 229 | 230 | 231 | 232 | 233 | 0.5000000000 234 | 0.5000000000 235 | 236 | 237 | 238 | 239 | 2.5868105888 240 | 0.5670760274 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | part two 278 | 279 | 280 | 281 | 0.300000 282 | 0.000000 283 | 0.000000 284 | 1.000000 285 | 286 | 287 | 288 | Sans Serif 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 0.2500000000 305 | 0.2500000000 306 | 307 | 308 | 309 | 310 | 0.5000000000 311 | 0.5000000000 312 | 313 | 314 | 315 | 316 | 0.4834947884 317 | -0.5177246332 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | part three 355 | 356 | 357 | 358 | 0.300000 359 | 0.000000 360 | 0.000000 361 | 1.000000 362 | 363 | 364 | 365 | Sans Serif 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 0.2500000000 382 | 0.2500000000 383 | 384 | 385 | 386 | 387 | 0.5000000000 388 | 0.5000000000 389 | 390 | 391 | 392 | 393 | -0.0666949674 394 | 1.1698374748 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | part four 432 | 433 | 434 | 435 | 0.300000 436 | 0.000000 437 | 0.000000 438 | 1.000000 439 | 440 | 441 | 442 | Sans Serif 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 0.2500000000 459 | 0.2500000000 460 | 461 | 462 | 463 | 464 | 0.5000000000 465 | 0.5000000000 466 | 467 | 468 | 469 | 470 | -2.6029005051 471 | 0.9125182033 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | part five 509 | 510 | 511 | 512 | 0.300000 513 | 0.000000 514 | 0.000000 515 | 1.000000 516 | 517 | 518 | 519 | Sans Serif 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 0.2500000000 536 | 0.2500000000 537 | 538 | 539 | 540 | 541 | 0.5000000000 542 | 0.5000000000 543 | 544 | 545 | 546 | 547 | 0.8887774348 548 | 0.7587298155 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | part six 586 | 587 | 588 | 589 | 0.300000 590 | 0.000000 591 | 0.000000 592 | 1.000000 593 | 594 | 595 | 596 | Sans Serif 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 0.2500000000 613 | 0.2500000000 614 | 615 | 616 | 617 | 618 | 0.5000000000 619 | 0.5000000000 620 | 621 | 622 | 623 | 624 | -2.1670331955 625 | -0.1035533398 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | --------------------------------------------------------------------------------