Separation of site from code

Working on separating the Lunatics site data from the
LunaGen source code, and replacing it with test code only.
This commit is contained in:
Terry Hancock 2022-06-28 01:27:13 -05:00
parent b836bef771
commit 158718a650
92 changed files with 628 additions and 1377 deletions

BIN
LunaGen/lunagen_profile.txt Normal file

Binary file not shown.

View File

479
LunaGen/src/lunagen_backup.py Executable file
View File

@ -0,0 +1,479 @@
#!/usr/bin/python3
# encoding: utf-8
'''
lunagen -- "Lunatics!" Project Release Site Generator
LunaGen generates a small static HTML website based on YAML
data documents containing the key release information. This
generates an index.html file containing the episode lists
for all series, individual release pages for each episode,
and additional pages for character list and story background.
This is meant to be a fan-focused site.
It also contains affiliate link, sponsor, advertising, and
other fundraising elements.
@author: Terry Hancock
@copyright: 2019 Anansi Spaceworks.
@license: GNU General Public License, version 2.0 or later. (Python code)
Creative Commons Attribution-ShareAlike, version 3.0 or later. (Website Templates).
@contact: digitante@gmail.com
'''
import sys
import os
#from shutil import copytree, rmtree
from distutils.dir_util import remove_tree, copy_tree
from optparse import OptionParser
import random
from collections import OrderedDict
import yaml
import jinja2
import lunagen
lunagen.demo()
class Config(object):
"""
Installation configuration variables.
TODO: Probably change this to load from a YAML file in the future.
"""
__all__ = []
__version__ = 0.1
__date__ = '2019-09-19'
__updated__ = '2019-09-19'
DEBUG = True
TESTRUN = False
PROFILE = False
THEMES = '../themes'
class LunaGen(jinja2.Environment):
"""
Generator for a LunaGen site, based on contents of srcdir:
<srcdir>/data - YAML files (handwritten content)
<srcdir>/templates - Jinja2 site templates
<srcdir>/skeleton - unchanging parts of the site (copied)
The new site is created in <tgtdir>.
"""
def __init__(self, srcdir, tgtdir=None, verbosity=0):
self.srcdir = os.path.abspath(srcdir)
if not tgtdir:
self.tgtdir = os.path.join(self.srcdir, 'site')
else:
self.tgtdir = os.path.abspath(tgtdir)
self.datadir = os.path.join(self.srcdir, 'data')
self.templates = os.path.join(self.srcdir, 'templates')
self.skeleton = os.path.join(self.srcdir, 'skeleton')
self.verbose = verbosity
if self.verbose:
print("Source directory: %s" % self.srcdir)
print("Target directory: %s" % self.tgtdir)
print("YAML content should be in: %s" % self.datadir)
print("Jinja2 templates should be in: %s" % self.templates)
print("Skeleton website should be in: %s" % self.skeleton)
#TODO: Could make sure these directories exist
# Load up the data from YAML files:
self._load_sitedata()
self._load_theme()
self._load_affiliates()
self._load_softwarelist()
self._load_products()
self._load_serieslist()
super().__init__(
loader=jinja2.ChoiceLoader([
jinja2.FileSystemLoader(os.path.join(self.datadir, 'templates')),
jinja2.FileSystemLoader(self.theme['path']),
jinja2.FileSystemLoader(self.templates)]),
autoescape=jinja2.select_autoescape(['html','xml']))
@staticmethod
def _paginate(seq, pagesize):
"""
Given a sequence of objects, break it into a book of
pages, each containing no more than pagesize objects:
>>> test = [1,'2','three', 4, 5, 'six', 'seven', 8, 9, True, False, None, 0]
>>> LunaGen._paginate(test, 4)
[[1, '2', 'three', 4], [5, 'six', 'seven', 8], [9, True, False, None], [0]]
>>>
"""
book = []
page = []
for i,ob in enumerate(seq):
if i%pagesize==0:
if i>0: book.append(page)
page = []
page.append(ob)
if len(page)>0:
book.append(page)
return book
@staticmethod
def _paginate_sponsors(series, episode):
"""
Regroup sponsors into pages which:
- Contain only one kind of sponsor
- Contain no more than the 'page' limit number of sponsors per page
- Are tagged with the sponsortype so we can find the right tempate for them:
>>> series = {'sponsortypes':
... {'A':{'page':1,'limit':1},
... 'B':{'page':3, 'limit':10},
... 'C':{'page':4, 'limit':20}}}
...
>>> episode = {'sponsors':
... {'A':list(range(2)),
... 'B':list(range(7)),
... 'C':list(range(22))}}
...
>>> LunaGen._paginate_sponsors(series, episode)
[('A', [0]), ('B', [0, 1, 2]), ('B', [3, 4, 5]), ('B', [6]), ('C', [0, 1, 2, 3]), ('C', [4, 5, 6, 7]), ('C', [8, 9, 10, 11]), ('C', [12, 13, 14, 15]), ('C', [16, 17, 18, 19])]
>>>
"""
paged_sponsors = []
for spkey, sponsortype in series['sponsortypes'].items():
if spkey not in episode['sponsors']:
episode['sponsors'][spkey] = []
#if 'excludes' in sponsortype:
# for excluded in sponsortype['excludes']:
# if excluded in episode['sponsors'] and episode['sponsors'][excluded]:
# print("WARNING: excluded sponsortype %s will be ignored, because of existing %s." %
# (excluded, spkey))
if 'page'in sponsortype:
paged = LunaGen._paginate(
episode['sponsors'][spkey][:sponsortype['limit']],
sponsortype['page'])
tags = [spkey] * len(paged)
paged_sponsors.extend(zip(tags, paged))
return paged_sponsors
@staticmethod
def _fix_series(series):
"""
Modify series data to correct certain datatypes that are not
natively supported by YAML (like OrderedDict):
>>> series = {'credits':{
... 'a':{'labels':'ordered'},
... 'b':{'labels':[['A','-A-'],['B','-B-']]}}}
...
>>> LunaGen._fix_series(series)
>>> series['credits']['b']['labels']
OrderedDict([('A', '-A-'), ('B', '-B-')])
>>>
"""
for key, credit in series['credits'].items():
if type(credit['labels']) != type(''):
credit['labels']=OrderedDict(credit['labels'])
def _collect_stylesheets(self, *extras):
"""
Collect a list of unique stylesheets from various stylesheet
requirements from theme, site, and data from extra pages.
"""
stylesheets = []
stylesheets.extend(self.theme['stylesheets'])
stylesheets.extend(self.sitedata['stylesheets'])
for extra in extras:
if 'stylesheets' in extra:
stylesheets.extend(extra['stylesheets'])
stylesheets = [s for i,s in enumerate(stylesheets) if s not in stylesheets[:i]]
return stylesheets
def _load_sitedata(self):
if self.verbose: print("Loading global site data.")
with open(os.path.join(self.datadir, 'site.yaml'), 'rt') as sitedatafile:
self.sitedata = yaml.load(sitedatafile)
def _load_theme(self):
if self.verbose: print("Loading theme data.")
self.theme = { 'stylesheets':[] } # Default values
themedir = os.path.join(Config.THEMES, self.sitedata['theme'])
if not os.path.exists(themedir):
raise FileNotFoundError("Theme directory %s not found!" % themedir)
with open(os.path.join(themedir, 'theme.yaml'), 'rt') as themedatafile:
self.theme.update(yaml.load(themedatafile))
self.theme['path'] = themedir
def _load_affiliates(self):
if self.verbose: print("Loading affiliates data.")
try:
with open(os.path.join(self.datadir, 'affiliates.yaml')) as aff_file:
affiliates = yaml.load(aff_file)
stylesheets = self.sitedata['stylesheets']
self.sitedata.update(affiliates)
self.sitedata['stylesheets'] = self._collect_stylesheets(affiliates)
self.sitedata['affiliates'] = random.sample(
affiliates['affiliates'], min( int(affiliates['affiliates_at_once']),
len(affiliates['affiliates'])))
except FileNotFoundError:
print("No affiliates.yaml file, so affiliates list is empty.")
self.sitedata['affiliates'] = []
def _load_softwarelist(self):
if self.verbose: print("Loading software data.")
try:
with open(os.path.join(self.datadir, 'software.yaml')) as sw_file:
softwarelist = yaml.load(sw_file)
stylesheets = self.sitedata['stylesheets']
self.sitedata.update(softwarelist)
self.sitedata['stylesheets'] = self._collect_stylesheets(softwarelist)
except FileNotFoundError:
print("No software.yaml file, so software list is empty.")
self.sitedata['softwarelist'] = []
def _load_products(self):
if self.verbose: print("Loading store products data.")
try:
with open(os.path.join(self.datadir, 'products.yaml')) as prod_file:
products = yaml.load(prod_file)
stylesheets = self.sitedata['stylesheets']
self.sitedata.update(products)
self.sitedata['stylesheets'] = self._collect_stylesheets(products)
except FileNotFoundError:
print("No products.yaml file, so software list is empty.")
self.sitedata['products'] = []
def _load_serieslist(self):
if self.verbose: print("Loading series data")
try:
with open(os.path.join(self.datadir, 'episodes', 'series.yaml'),'rt') as seriesfile:
self.serieslist = yaml.load(seriesfile)['serieslist']
for series in self.serieslist:
self._fix_series(series)
episodes = []
seriesdir = os.path.join(self.datadir, 'episodes', series['directory'])
episode_filenames = [f for f in os.listdir(seriesdir) if f.endswith('.yaml')]
for episode_filename in episode_filenames:
if self.verbose: print("Loading episode from %s" % episode_filename)
with open(os.path.join(seriesdir, episode_filename), 'rt') as episode_file:
episodes.append(yaml.load(episode_file))
# Sort by episode number specified in the files:
try:
episodes.sort(key=lambda a: int(a['episode']))
except KeyError:
print("Some episode YAML files may not have an 'episode' number entry?")
series['episodes'] = episodes
except FileNotFoundError:
print("No series.yaml file, so no series loaded.")
self.sitedata['serieslist'] = []
def _copy_skeleton(self):
if os.path.exists(self.tgtdir):
remove_tree(self.tgtdir, verbose=self.verbose)
if self.verbose: print("Copying the theme base.")
copy_tree(os.path.join(self.theme['path'], 'base'), self.tgtdir, verbose=self.verbose)
if self.verbose: print("Copying the skeleton site.")
copy_tree(self.skeleton, self.tgtdir, verbose=self.verbose)
def _gen_simple_page(self, pagename, stylesheets=()):
"""
Generates a simple page with 1:1:1 Jinja2+YAML+CSS.
The YAML and CSS pages are optional. If no file exists
for them, they will be ignored and the page generated
with only the template and global data and/or style.
"""
if self.verbose: print("Creating '%s' page" % pagename)
jinja2_name = pagename + '.j2'
yaml_path = os.path.join(self.datadir, pagename+'.yaml')
css_path = os.path.join(self.tgtdir, pagename+'.css')
# Assumes skeleton already copied
data = {} # Defaults can be set here
data.update(self.sitedata) # Global data
if os.path.exists(yaml_path):
with open(yaml_path, 'rt') as yaml_file:
data.update(yaml.load(yaml_file)) # Page data
data['stylesheets'] = self._collect_stylesheets(data)
# Add CSS if not already present:
if os.path.exists(css_path) and pagename not in data['stylesheets']:
data['stylesheets'].append(pagename)
if self.verbose: print("Generating '%s.html' from template." % pagename)
html = self.get_template(jinja2_name).render(data)
with open(os.path.join(self.tgtdir, pagename+'.html'), 'wt') as page:
page.write(html)
def _gen_index(self):
"""
Generate an index page, if the skeleton doesn't already have one.
"""
if not os.path.exists(os.path.join(self.tgtdir, 'index.html')):
if self.verbose: print("Generating the Index page.")
data = {}
data.update(self.sitedata)
data['next'] = next # Adds iterator capability
data['stylesheets'] = self._collect_stylesheets(self.sitedata['stylesheets'])
if 'episode_as_index' in self.sitedata and self.sitedata['episode_as_index']:
data['serieslist'] = self.serieslist
data['banners'] = iter(['affiliates_banner.j2',
'store_banner.j2',
'sponsoropps_banner.j2'])
data['stylesheets'].extend(self._collect_stylesheets(
self.sitedata['episode_list_page']))
html = self.get_template('index.j2').render(data)
with open(os.path.join(self.tgtdir, 'index.html'), 'wt') as page:
page.write(html)
else:
if self.verbose: print("Found 'index.html', so not generated.")
def _gen_episode_list_page(self):
"""
Generate a page linking to all of the individual episodes,
grouped into "series" (or "seasons").
#Currently hard-coded to be saved as 'index.html' for the site.
"""
if self.verbose: print("Generating the Index (Episode List) page.")
if 'render_as' in self.sitedata['episode_list_page']:
render_as = self.sitedata['episode_list_page']['render_as']
else:
render_as = 'index.html'
data = {}
data.update(self.sitedata)
data['serieslist'] = self.serieslist
data['banners'] = iter(['affiliates_banner.j2', 'store_banner.j2', 'sponsoropps_banner.j2'])
data['stylesheets'] = self._collect_stylesheets(self.sitedata['episode_list_page'])
data['next'] = next # Adds iterator capability
html = self.get_template('episode_list.j2').render(data)
with open(os.path.join(self.tgtdir, render_as), 'wt') as page:
page.write(html)
def _gen_episode_pages(self):
"""
Generate a page for each episode in each series.
"""
if self.verbose: print("Generating episode pages...")
if 'stylesheets' in self.sitedata['episode_pages']:
stylesheets = self.sitedata['episode_pages']['stylesheets']
else:
stylesheets = []
for series in self.serieslist:
for episode in series['episodes']:
paged_sponsors = self._paginate_sponsors(series, episode)
episode['paged_sponsors'] = iter(paged_sponsors)
data = {}
data.update(self.sitedata)
data['series'] = series
data['episode'] = episode
data['stylesheets'] = self._collect_stylesheets(self.sitedata['episode_pages'],episode)
data['next'] = next
data['banners'] = ['affiliates_banner.j2']
html = self.get_template('episode_page.j2').render(data)
filename = episode['series'] +'E' + ('%2.2d' % int(episode['episode'])) + '.html'
os.makedirs(os.path.join(self.tgtdir, series['directory']), exist_ok=True)
with open(os.path.join(self.tgtdir, series['directory'], filename), 'wt') as page:
page.write(html)
def gensite(self):
"""
Generate the site, using the data we've accumulated.
"""
self._copy_skeleton()
for page in self.sitedata['simple_pages']:
self._gen_simple_page(page)
if self.sitedata['serieslist']:
self._gen_episode_list_page()
self._gen_episode_pages()
else:
print("Not generating series & episode pages: serieslist empty.")
self._gen_index()
def main(argv=None):
'''Command line options.'''
program_name = os.path.basename(sys.argv[0])
program_version = "v0.1"
program_build_date = "%s" % Config.__updated__
program_version_string = '%%prog %s (%s)' % (program_version, program_build_date)
program_longdesc = '''\
LunaGen is a static HTML website generator designed for releasing
a series of episodes (technically: a series of series of episodes).
Data is authored using the YAML structured data language, which allows
for episode metadata and descriptions to be written in a human-friendly
format, which is then formatted into HTML using Jinja2 templates.
Once generated, the site is static and can simply be uploaded to a
standard web server with minimal or no configuration (like a static web host).
It was originally created to generate the release pages for
Anansi Spaceworks' "Lunatics!" series.
For details, please see the 'examples' and 'doc' directories.
'''
program_license = "Copyright 2019 Terry Hancock (Anansi Spaceworks) \
Licensed under the GNU General Public License, version 2.0\n"
if argv is None:
argv = sys.argv[1:]
#try:
# setup option parser
parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license)
parser.add_option("-i", "--in", dest="src", help="set input path [default: %default]", metavar="FILE")
parser.add_option("-o", "--out", dest="tgt", help="set output path [default: %default]", metavar="FILE")
parser.add_option("-v", "--verbose", dest="verbose", action="count", help="set verbosity level [default: %default]")
# set defaults
parser.set_defaults(tgt="./site", src=".")
# process options
(opts, args) = parser.parse_args(argv)
if opts.verbose > 0:
print("verbosity level = %d" % opts.verbose)
if opts.src:
print("src = %s" % opts.src)
if opts.tgt:
print("tgt = %s" % opts.tgt)
lunagen = LunaGen(opts.src, opts.tgt, opts.verbose)
lunagen.gensite()
# except Exception as e:
# indent = len(program_name) * " "
# sys.stderr.write(program_name + ": " + repr(e) + "\n")
# sys.stderr.write(indent + " for help use --help")
# return 2
if __name__ == "__main__":
if Config.DEBUG:
#sys.argv.append("-h")
sys.argv.append("-v")
if Config.TESTRUN:
import doctest
doctest.testmod()
if Config.PROFILE:
import cProfile
import pstats
profile_filename = 'lunagen_profile.txt'
cProfile.run('main()', profile_filename)
statsfile = open("profile_stats.txt", "wb")
p = pstats.Stats(profile_filename, stream=statsfile)
stats = p.strip_dirs().sort_stats('cumulative')
stats.print_stats()
statsfile.close()
sys.exit(0)
sys.exit(main())

View File

@ -1,55 +0,0 @@
# Affiliates Ad data
# This is a MOCK-UP, not real affiliates.
# (Because I don't have any yet)
---
stylesheets:
- affiliates
# It's awkward to have too many affiliate links at one time, so
# the script will choose a number of them at random on each refresh
# we need to tell it how many we want:
affiliates_at_once: 7
affiliates:
- name: 123posters
url: http://www.123posters.com/
img: 123posters.png
- name: Amazon
url: http://amazon.com
img: amazon.png
- name: Barnes & Noble
url: https://www.barnesandnoble.com/
img: barnes_noble.png
- name: Better World Books
url: https://www.betterworldbooks.com/
img: betterworldbooks.png
- name: Crayola
url: https://www.crayola.com/
img: crayola.png
- name: Humble Bundle
url: https://www.humblebundle.com/
img: humble.png
- name: Kiwi Crate
url: https://www.kiwico.com/
img: kiwicrate.png
- name: Linux Foundation Training Programs
url: https://www.linuxfoundation.org/
img: linux_foundation.png
- name: Lonely Planet
url: https://www.lonelyplanet.com/
img: lonelyplanet.png
- name: Red Hat
url: https://www.redhat.com/en
img: redhat.png
- name: Lenovo
url: https://www.lenovo.com/us/en/pc
img: lenovo.png

View File

@ -1,147 +0,0 @@
# Affiliates Ad data
# This is a MOCK-UP, using popular Free Culture / Open Source projects
# We do not have any actual affiliation with these sites
#
---
stylesheets:
- affiliates
# It's awkward to have too many affiliate links at one time, so
# the script will choose a number of them at random on each refresh
# we need to tell it how many we want:
affiliates_at_once: 7
affiliates_message: >
We benefit when you support these projects and services!
affiliates:
- name: Ardour
url: https://ardour.org
img: ardour.png
type: Software We Use
- name: Audacity
url: https://audacityteam.org
img: audacity.png
type: Software We Use
- name: Blender
url: https://blender.org
img: blender.png
type: Software We Use
- name: Blender Cloud
url: https://cloud.blender.org
img: blender_cloud.png
type: Open Movie Platform
- name: BlendSwap
url: https://blendswap.com
img: blendswap.png
type: Shared Asset Archive
- name: Creative Commons
url: https://www.creativecommons.org
img: creativecommons.png
type: Free Culture Advocacy Group
- name: Debian
url: https://debian.org
img: debian.png
type: Software We Use
- name: Ethic Cinema
url: http://ethiccinema.org
img: ethiccinema.png
type: Free Culture Advocacy Group
- name: Free Software Foundation
url: https://www.fsf.org
img: freesoftwarefoundation.png
type: Free Software Advocacy Group
- name: Free Sound
url: https://freesound.org
img: freesound.png
type: Shared Asset Archive
- name: GIMP
url: https://gimp.org
img: gimp.png
type: Software We Use
- name: Inkscape
url: https://inkscape.org
img: inkscape.png
type: Software We Use
- name: Internet Archive
url: https://archive.org
img: internet_archive.png
type: Shared Asset Archive
- name: Jamendo
url: https://jamendo.com
img: jamendo.png
type: Shared Asset Archive
- name: Kdenlive
url: https://kdenlive.org
img: kdenlive.png
type: Software We Use
- name: Krita
url: https://krita.org
img: krita.png
type: Software We Use
- name: Mastodon
url: http://mastodon.social
img: mastodon.png
type: Social Media Platform We Use
- name: Morevna Project
url: https://morevnaproject.org
img: morevnaproject.png
type: Free Culture Project
- name: MusOpen
url: https://musopen.org
img: musopen.png
type: Shared Asset Archive
- name: Olive Video Editor
url: https://olivevideoeditor.org
img: olivevideoeditor.png
type: Software We Use
- name: Public Domain Files
url: http://www.publicdomainfiles.com
img: publicdomainfiles.png
type: Shared Asset Archive
- name: Public Knowledge
url: https://publicknowledge.org
img: publicknowledge.png
type: Free Culture Advocacy Group
- name: Question Copyright
url: https://questioncopyright.org
img: questioncopyright.png
type: Free Culture Advocacy Group
- name: Software Freedom Conservancy
url: https://sfconservancy.org
img: sfconservancy.png
type: Free Software Advocacy Group
- name: Ubuntu Studio
url: https://ubuntustudio.org
img: ubuntu_studio.png
type: Software We Use
- name: Urchn
url: https://urchn.org
img: urchn.png
type: Free Culture Studio

View File

@ -1,227 +0,0 @@
# Data for the character list page
---
stylesheets:
- bkg_moon_iridium
characters:
- name: Hiromi
fullname: Hiromi Aoki Lerner
img: hiromi_render_portrait.png
born: 2001
first_episode: 1
desc: >
Hiromi Lerner was a medical student and a concert
pianist when she first learned about the International
Space Foundation's project to put a colony on the Moon.
She's had a life-long interest in space (stemming partly
from her father, who owns a small aerospace business in
Torrance, California), but her main motivation is to
humanize the project and establish a comfortable home
on the Moon. She idolizes her husband Rob Lerner, but
also recognizes that he often has his head in the clouds,
and is not always the most stable man. She does a lot to
keep him grounded.
She's a traditionalist when it comes to home, decor,
and fashion, and has an active artistic interest in
integrating Japanese traditionalist aesthetics with
her technological lifestyle. She also sometimes sees
herself in terms of American western pioneers, who she
feels a lot of connection with as a pioneer on a new
frontier.
relations:
husband: Rob Lerner
daughter: Georgiana Lerner
father: David Aoki
mother: Ryoko Takamura
- name: Rob
fullname: John Robert Lerner
img: rob_render_portrait.png
born: 1993
first_episode: 2
desc: >
Rob Lerner is a dreamer who has always been fascinated
with the idea of living on another world. He grew up
reading (and watching) science fiction, and chose
mechanical engineering as a major in college. He's originally
from Texas, although he's lived in many parts of the US
Southwest during his life.
He tends to be focused on big picture ideas, although he
can also become bogged down in technical details and tends
to see problems through his engineering lens, which sometimes
leads him to struggle with more organic problems. His personality
is somewhat mercurial, prone to manic obsessions and depression
when things don't work out as he hopes.
He has a great enthusiasm for humanity in the abstract, but
often finds real individual humans hard to get along with. He
doesn't quite understand what he did to deserve the loyalty
and affection of Hiromi, but he's determined to keep doing it.
He founded the International Space Foundation which sponsors
the colony, and his place there is largely connected to that
status. He's seen as a kind of spiritual leader to the rest,
but isn't necessarily formally in charge.
relations:
wife: Hiromi Lerner
daughter: Georgiana Lerner
- name: Anya
fullname: Anna Anatolyeva Titova / Anna Titova-Farmer (US)
img: anya_render_portrait.png
born: 2005
first_episode: 3
desc: >
Anya Titova (also known as Anya Farmer or Anya Titova-Farmer,
but she uses her well-known maiden name professionally) is
the youngest child and only daughter of the billionaire co-owner of
a major space company (RK Cosmos), Anatoly Titov (his brother
Konstantin is also a major shareholder, as well being largely
in charge of research and development), and granddaughter of
Igor Titov who founded the company.
Unlike her older brothers, Sergei and Gregor, she was not
pressured to work as a cosmonaut, although she has had
extensive pilot training as a kind of family expectation.
She's lived a very privileged life, and was very much a rich
party girl in her youth. She liked fast cars and flashy
technology, but has increasingly wanted to make an identity
for herself. In her early twenties, she established her own
satellite company, and has used the profits to press her
new interest in space development.
She met Josh Farmer at a conference on space agriculture
sponsored by Rob Lerner's ISF, and there was immediate
chemistry her and Josh, which led to them getting married
and having a child not long after. But Anya never really
slowed down in her career, simply turning her efforts
more and more directly towards the ISF project.
Her family regards her interest in this project amusingly
"domestic", and tease her about it, but this will not stop
her. The more menial aspects of frontier life are a hard
challenge for her to adapt to, but she's managing.
She and Rob became co-conspirators on founding the project,
and she largely brought in the money and the corporate
connections to make it happen -- partly on the condition that
she and her family are in on this first colony.
relations:
husband: Josh Farmer
son: Tim Farmer
father: Anatoly Titov
brothers:
- Sergei Titov
- Gregor Titov
grandfather: Igor Titov
- name: Josh
fullname: Joshua Randall Farmer
img: josh_render_portrait.png
born: 2004
first_episode: 2
desc: >
Josh Farmer is amused at his appropriate name, as he
is indeed from a family of Farmers in the US Midwest. He
has a lot of Middle-American attitudes and values, but
he is the one who went to college. In fact, he has a
doctorate in agricultural science, though he doesn't
really like to be called "Doctor Farmer".
His specialty is space agriculture, and he knows just
about everything that anyone knows about how to grow
crops in space, and he's got a pretty fair knowledge of
animal husbandry as well, largely from his youth, growing
up on a farm.
He and his wife Anya seem like complete opposites, but
in fact, they share a lot of basic values, and so their
relationship is more complementary than oppositional. But
it's no question that Josh's quiet stoic outlook on life
makes an intense contrast with Anya's firebrand personality
and outspokenness.
Rob Lerner recruited Josh early on in the Lunar Independent
Biospherics Research Experiment (LIBRE) -- the Earthly
prototype the ISF colony is based on, early on, and he
has been instrumental to the planning and operation of the
project.
relations:
wife: Anya Titova
son: Tim Farmer
- name: Tim
fullname: Igor Timothy Farmer
img: tim_render_portrait.png
born: 2026
first_episode: 2
desc: >
Tim does not like his first name, Igor, which Anya gave
him in honor of his great-grandfather, and prefers to
stick with Tim. His mother still calls him "Igor", though.
He did not exactly choose to be a Lunar colonist, though
his chosen form of rebellion (promoting Mars colonization)
betrays a bit of his pride in his family and situation. It's
also true that he was old enough to have stayed with one
of his uncles' families had he really not wanted to go.
He is very much aware of the social deprivation of being
"literally the only teenager on the entire planet", and
he spends a lot of his time online, interacting with his
friends in VR communities on the "Great Global Grid" which
he jokes is now th "Great Galactic Grid". He has a large
following online, including a lot of teenage girls, who
he has a not-entirely-ethical attitude towards, much to
his mother's frustration, and a somewhat unhealthy connection
to a piece of anime-inspired "virtual girlfriend" software,
known as "Momo Aiai".
He spends a lot of time hanging out with Georgiana, who is
a little like a sister, given how close their living
arrangements are, but they're too far apart in age to have
really common interests.
relations:
father: Josh Farmer
mother: Anya Titova
- name: Georgiana
fullname: Georgiana Lerner
img: georgiana_render_portrait.png
born: 2033
first_episode: 1
desc:
Georgiana is a very quiet, introspective girl, who likes to
play with her bunny doll and secretly misses Earth, though
she's also excited about the prospect of living on the Moon,
and doesn't want to disappoint her parents.
She's still at a young enough age for magical thinking, and
sometimes invents her own "mythology" to explain how her
world works, ranging from insightful to woefully incorrect,
as with most children her age.
She's eager to learn new skills and adapts pretty quickly to
life in the colony.
relations:
mother: Hiromi Lerner
father: Rob Lerner

View File

@ -1,35 +0,0 @@
# Gumroad Store product links
---
stylesheets:
- products
products:
- name: Books
desc: Artbooks, writers guide, and more.
url: http://gumroad.com/anansispace
img: store_books.png
- name: Hard Media
desc: Video on DVD or flash media. Soundtrack on CD or vinyl.
url: http://gumroad.com/anansispace
img: store_hardmedia.png
- name: Posters
desc: Decorate your wall with posters and banners.
url: http://gumroad.com/anansispace
img: store_posters.png
- name: Subscriptions
desc: Subscribe to keep up to date, or become a patron for one episode.
url: http://gumroad.com/anansispace
img: store_subscriptions.png
- name: Novelties
desc: Collectible novelties and toys.
url: http://gumroad.com/anansispace
img: store_novelties.png
- name: Digital Perks
desc: Wallpapers, digital design, 3D printing, custom products.
url: http://gumroad.com/anansispace
img: store_digitalperks.png

View File

@ -1,36 +1,22 @@
# Global data for the site
# ALL templates will have access to these
---
sitename: lunatics
sitetitle: Lunatics!
sitename: series_test
sitetitle: Series Test
imgdir: images
theme: lunatics
stylesheets:
- products
- affiliates
theme: default
episode_list_page:
render_as: index.html
stylesheets:
- main
- credits
- affiliates
- series
- products
- bkg_moon_iridium
episode_pages:
stylesheets:
- main
- credits
- affiliates
- products
- episode
- sponsors
simple_pages:
- characters
- universe
- sponsorship

View File

@ -1,139 +0,0 @@
# Software list
---
softwarelist:
- name: Aegisub
desc: Subtitle editor
url: http://www.aegisub.org/
img: aegisub.png
- name: Ansible
desc: Provisioning automation system
url: https://www.ansible.com/
img: ansible.png
- name: Ardour
desc: Digital audio workstation
url: http://ardour.org/
img: ardour.png
- name: Audacity
desc: Audio recorder and editor
url: http://web.audacityteam.org/
img: audacity.png
- name: Brasero
desc: Optical disk burning utility
url: https://wiki.gnome.org/Apps/Brasero
img: brasero.png
- name: DVDStyler
desc: DVD mastering tool
url: https://www.dvdstyler.org/en/
img: dvdstyler.png
- name: GIMP
desc: Image Manipulation Package
url: http://www.gimp.org/
img: gimp.png
- name: Inkscape
desc: 2D vector graphic editing application
url: http://www.inkscape.org/
img: inkscape.png
- name: Kdenlive
desc: Non-linear video editor
url: http://kdenlive.org/
img: kdenlive.png
- name: Krita
desc: Digital painting application
url: http://www.krita.org/
img: krita.png
- name: LibreOffice
desc: Office Suite
url: https://www.libreoffice.org
img: libreoffice.png
- name: MediaWiki
desc: Collaborative document editing platform
url: http://mediawiki.org/
img: mediawiki.png
- name: Mumble
desc: VOIP teleconferencing package
url: https://wiki.mumble.info/
img: mumble.png
- name: MyPaint
desc: Digital painting application
url: http://mypaint.org/about/
img: mypaint.png
- name: OBS Studio
desc: Screencasting package
url: https://obsproject.com/
img: obs.png
- name: Papagayo-NG
desc: Lipsinc animation editor
url: https://morevnaproject.org/papagayo-ng/
img: papagayo-ng.png
- name: RapidSVN
desc: Subversion (version control) client
url: https://rapidsvn.org/
img: rapidsvn.png
- name: RenderChan
desc: Render management system
url: https://morevnaproject.org/renderchan
img: renderchan.png
- name: Resource Space
desc: Digital asset management system
url: https://www.resourcespace.com/
img: resourcespace.png
- name: Subversion
desc: Version control system
url: https://subversion.apache.org/
img: subversion.png
- name: Synfig
desc: 2D animation package
url: http://synfig.org/
img: synfig.png
- name: TACTIC
desc: Digital asset and project management system
url: https://southpawtech.com/tactic-open-source/
img: tactic.png
- name: Trac
desc: Version control web interface (for Subversion)
url: https://trac.edgewall.org/
img: trac.png
- name: Ubuntu Studio
desc: Multimedia distribution of GNU/Linux
url: https://ubuntustudio.org/
img: ubuntustudio.png
- name: VLC
desc: Media player
url: https://www.videolan.org/index.html
img: vlc.png
- name: Vokoscreen
desc: Screencasting software
url: http://linuxecke.volkoh.de/vokoscreen/vokoscreen.html
img: vokoscreen.png
- name: Wordpress
desc: Content management system
url: https://wordpress.org/
img: wordpress.png

View File

@ -1,140 +0,0 @@
---
stylesheets:
- sponsorship
individual:
- name: Donation
amount: "Any"
gumroad_url: "https://gumroad.com/anansispace#ZCNlG"
patreon_url: "https://www.patreon.com/lunatics"
embed_template: paypal_donate_button
img: donations.png
description: >
If you just want to help us out, we can take simple donations of any amount
via our Gumroad store (once only), Patreon Memberships (multiple tiers
per-episode), or by making a one-time donation via PayPal.
- name: Patron
amount: "$20.00"
gumroad_url: "https://gumroad.com/anansispace#sumVF"
patreon_url: "https://www.patreon.com/lunatics"
img: patron_sponsor.png
description: >
Patron sponsors' names are listed at the end of the credits. You
may use a pseudonym, collective name, or business name if you like,
as long as there's no offensive language or messages included.
This is included with Patreon memberships of $20 per episode or more,
or can be acquired for one episode via our Gumroad account.
- name: Silver Sponsorship
amount: "$50.00"
gumroad_url: "https://gumroad.com/anansispace#sumVF"
patreon_url: "https://www.patreon.com/lunatics"
img: silver_sponsor.png
description: >
Silver sponsors get their names in a prominent listing after the
Gold sponsors.
- name: Gold Sponsorship
amount: "$100.00"
gumroad_url: "https://gumroad.com/anansispace#sumVF"
patreon_url: "https://www.patreon.com/lunatics"
img: gold_sponsor.png
description: >
Gold sponsors get their names in a prominent listing, after commercial
sponsorships, but before all other individual sponsorships. They will
also be listed in the credits on the download page, but they will not
be linked. You may use a company or collective name or a pseudonym rather
than your legal name, if you like. Please no foul language, slogans, or
political messages here, though.
- name: Film Freedom Monthly Subscription
amount: "$3+"
patreon_url: "https://www.patreon.com/filmfreedom"
img: filmfreedom_subscription.png
description: >
If you are mainly interested in our technical, development, and documentation
work, and like the predictability of a monthly subscription, please consider
supporting our <a href="http://filmfreedom.org">Film Freedom</a> project on
Patreon. We're starting a podcast to share our experiences with free culture
and free licenses software for production work, as well as sharing video and
written tutorials, research, and hints.
commercial:
- name: Commercial Sponsorship
amount: "$300.00"
gumroad_url: "https://gumroad.com/anansispace#chuEA"
email_address: sales@anansispaceworks.com
img: commercial_sponsor.png
description: >
Commercial sponsors get a space for a large monochrome logotype graphic
in the credits. They will appear in clustered groups alongside the credits,
rendered in the same color as the credits, and listed after the
"Commercial Logo" sponsorships (but before any of the individual sponsorships).
These logos will also be included in the credits list on the episode download
page on our website, and you may provide a URL to link the logo to from the
web page.
- name: Logo Commercial Sponsorship
amount: "$1000.00"
gumroad_url: "https://gumroad.com/anansispace#chuEA"
email_address: sales@anansispaceworks.com
img: logo_sponsor.png
description: >
Logo sponsors will get a square space beside or ahead of the end credits in the
episode, and a prominently-placed logo in the download page for the episode on our
website. You may provide a URL to link to from the logo for the website.
The logo art will appear at 480x480 px at our nominal 1920x1080 production frame,
although scaling to online video sites may make it appear smaller, so you should
plan accordingly (use a bold design and large enough font).
Your logo will typically appear alongside other logo sponsors in the credits. We
limit the number of possible logo spots to a maximum of 12.
corporate:
- name: Anorthosite Principal Episode Sponsorship
amount: "$25,000"
email_address: mailto:sales@anansispaceworks.com
img: anorthosite_sponsor.png
description: >
We estimate that it costs a minimum of $25,000 to produce an episode of "Lunatics!",
which of course, is very inexpensive by TV animation standards, but is enabled by our
low overhead, free-software pipeline, and virtual studio approach.
For this amount, we offer the maximum sponsorship exposure, with an animated logo
flown right at the beginning, prior even to our own "Anansi Spaceworks" production
logo. There will only be one such sponsor accepted for an episode, so the placement
is exclusive.
If you do not have a prepared logo, we can "fly" a simple logo for you, based
on a simple graphic trademark or corporate logo from you.
You will also get a prominent placement ahead of credits on our episode download
page, which can be linked to your company's website.
- name: Breccia Associate Episode Sponsorship
amount: "$10,000"
email_address: mailto:sales@anansispaceworks.com
img: breccia_sponsor.png
description: >
If a full episode sponsorship is a little too high, we also have an "Associate"
sponsorship for half the price, with only a slightly less prominent placement, after
our initial production logo, but still before the episode starts.
Obviously, this one is not exclusive, though we won't take more than three associate
sponsorships for an episode, or one with a full-episode sponsorship.
As with the episode sponsorship, you can provide us with a logo animation, or we
can do a simple animation for you, based on your logo art.
You will also get a prominent placement ahead of the credits on our episode download
page, which can be linked to your company's website.

View File

@ -1,28 +1,25 @@
# Episode release data
# See also series.yaml for documentation of options
---
series: S1
series: LunaGen Intro
episode: 1
mode: production
title: "Prolog: 'No Children in Space'"
poster: s1e01-prolog-600sq.png
status: Finishing and Rendering Sequences. Coming Soon!
title: "Yaml Tutorial | Learn YAML in 18 mins"
poster: s1e01-yaml_tut_yt-nana-600sq.png
status: YAML Intro From YouTube
stylesheets:
- bkg_earth_limb
description: >
In the first episode of this hard-science-fiction fairytale, seven-year-old
Georgiana Lerner becomes the first child in space, while her mother grapples
with press questions about the ethics of raising children in space. Both fly
into orbit aboard a Soyuz launch vehicle from the same launchpad as Yuri
Gagarin took 79 years earlier, on the first leg of their journey to the Moon
to join the first true extra-terrestrial settlement.
In lieu of a custom tutorial for LunaGen, this is a tutorial on YouTube
for YAML syntax, presented by "TechWorld with Nana" I'm also testing
embedding a video from YouTube.
videos:
- id: preview
name: Preview
url: https://player.vimeo.com/video/350535112
btn: s1e01-preview-btn.png
- id: tutorial
name: Tutorial
url: https://www.youtube.com/watch?v=1uFVr15xDGg
btn: s1e01-yaml_tut_yt-nana-60btn.png
product:
img: om_dvd.png
@ -43,261 +40,22 @@ product:
sponsors:
full_sponsor:
- name: Film Freedom Foundation
url: http://www.filmfreedom.org
img: film_freedom_foundation.png
full_sponsor: []
logo_sponsors:
- name: New Worlds Institute
url: https://earthlightfoundation.org/newworlds/
img: newworlds-600px.png
- name: Indian Pirate Party
url: http://pirates.org.in
img: indian_pirates-600px.png
- name: The Luna Project
url: http://www.lunarcc.org
img: luna_project-600px.png
logo_sponsors: []
corp_sponsors: []
gold_sponsors: []
silver_sponsors:
- Chris Kuhn
silver_sponsors: []
patron_sponsors:
- Tobias Platen
- Shiva Wolfe
- Filip Krška
- Morevna Project
- Nigel Waite
- Chris Kuhn
patron_sponsors: []
backer_sponsors:
- Martin Ansdell-Smith
- Jim Bedford
- Mauro Bieg
- Ryan Cartwright
- John Colagioia
- Tom Curl
- Konstantin Dmitriev
- Jonathan Fluck
- Joe Futrelle
- Thomas Gideon
- Gal Goldschmidt
- Franz Gustav
- Janet Hancock
- Simon Harris
- Ian
- Javier
- Norman Jolley
- David Jordan
- Emmanouel Kapernaros
- Kathryn N Kapust
- Tom Kiesel
- Mike Linksvayer
- Raffaele Manzo
- David Maulik
- Donna & John Myers
- John Myers
- Stephen Ormsby
- Derek Pearcy
- Vibha Pingle
- Mike Riley
- Gregor B. Rosenauer
- Timo Rossi
- Karen Sandler
- Hugo Simões
- Elizabeth Small
- Barry Solow
- Junius Stone
- Julie Tribble
- John Tyler
- Hal Tynan
- Stuart Ward
- Robert Wicks
- Shiva Wolfe
- Joseph Yu
- mm2001
backer_sponsors: []
credits:
main_credits:
-
- Screenplay
- Rosalyn Hunter
-
- Produced & Directed
- Terry Hancock
-
- Character Design
- Daniel Fu
main_credits: []
cast_credits:
-
- Narrator
- Melodee M. Spevack
-
- Hiromi Lerner
- Karrie Shirou
-
- Georgiana Lerner
- Ariel Hancock
-
- Boy on Train
- Shamil Aminov
-
- Mother on Train
- Nadezhda Dmitrieva
-
- 'Reporter #1 (USA)'
- Terry Hancock
-
- 'Reporter #2 (Canada)'
- Jami Cullen