Skip to content

Instantly share code, notes, and snippets.

@DanielMuller
Created July 29, 2014 18:54
Show Gist options
  • Save DanielMuller/a8e855ab1516e6807762 to your computer and use it in GitHub Desktop.
Save DanielMuller/a8e855ab1516e6807762 to your computer and use it in GitHub Desktop.
Create a timelapse of a region from your OpenTTD game using the saved games
#!/usr/bin/python
import argparse
import glob
import os
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
from distutils.spawn import find_executable
import time
import re
import locale
class OpenTTDTimelapse:
args = None
autoexec_script_name = "autoexec.scr"
gamestart_script_name = "game_start.scr"
autoexec_script_path = None
gamestart_script_path = None
screenshot_path = None
save_path = None
ottd = None
openttd = None
ffmpeg = None
backup_autoexec = False
backup_gamestart = False
def __init__(self, args):
self.args = args
self.define_paths()
def define_paths(self):
self.ottd = os.path.realpath(os.path.expanduser(self.args.ottd))
self.autoexec_script_path = os.path.join(self.ottd,"scripts", self.autoexec_script_name)
self.gamestart_script_path = os.path.join(self.ottd,"scripts", self.gamestart_script_name)
self.screenshot_path = os.path.join(self,self.ottd,"screenshot")
self.save_path = os.path.join(self.ottd,"save")
if not os.path.isdir(self.ottd):
print "No such folder %s" % self.ottd
exit(1)
if not os.path.isdir(os.path.join(self.ottd,"save")):
print "Folder %s is not an OpenTTD folder" % self.ottd
exit(1)
self.openttd = find_executable("openttd")
if self.openttd is None:
print "openttd not found, please install openttd"
exit(1)
self.ffmpeg = find_executable("ffmpeg")
if self.ffmpeg is None:
self.ffmpeg = find_executable("avconv")
if self.ffmpeg is None:
print "ffmpeg or avconv not found, please install ffmpeg or libav-tools"
exit(1)
def get_file_date(self, filename):
return time.strptime(" ".join(re.split("^([0-9]+)[a-z]*\s([a-zA-Z]+)\s([0-9]+)$",os.path.basename(filename).split(',')[-1].split('.')[0].strip())[1:4]), "%d %b %Y")
def get_save_game(self):
files = sorted(glob.glob(os.path.join(self.save_path, "%s*.sav" % self.args.company)), key=lambda x: time.strftime("%Y%m%d",self.get_file_date(x)))
return files
def backup_script(self, script_path):
backup = False
if os.path.isfile(script_path):
backup = True
os.rename(script_path, script_path+".orig")
return backup
def make_screenshot_script(self, screenshot_name):
f = open(self.gamestart_script_path,"w")
f.write("screenshot giant "+screenshot_name+os.linesep+"exit")
f.close()
def get_crop_info(self, im):
width0, height0 = im.size
width1 = self.args.width*self.args.zoom
height1 = self.args.height*self.args.zoom
left1 = int(round((width0*self.args.left/100)-(width1/2)))
top1 = int(round((height0*self.args.top/100)-(height1/2)))
right1 = left1+width1
bottom1 = top1+height1
if right1>width0:
delta = right1-width0
left1 = left1-delta
if bottom1>height0:
delta = bottom1-height0
bottom1 = bottom1-delta
if right1<0:
right1=0
if top1<0:
top1=0
return (left1, top1, right1, bottom1)
def draw_date(self, im1, game_file):
txt = time.strftime("%Y",self.get_file_date(game_file))
txt_margins = (int(round(self.args.width*0.015)),int(round(self.args.height*0.015)))
draw = ImageDraw.Draw(im1)
font_size = int(round(self.args.height/20))
font = ImageFont.truetype(self.args.font_path, font_size)
txt_size = font.getsize(txt)
txt_pos = (self.args.width-txt_margins[0]-txt_size[0], self.args.height-txt_margins[1]-1.5*txt_size[1])
draw.text(txt_pos,txt,(255,255,255),font=font)
return im1
def clean(self):
if not self.args.debug:
if os.path.isfile(self.gamestart_script_path):
os.unlink(self.gamestart_script_path)
for frame_image in glob.glob(os.path.join(self.screenshot_path,"frame_*.png")):
os.unlink(frame_image)
if self.backup_autoexec:
os.rename(self.autoexec_script_path+".orig", self.autoexec_script_path)
if self.backup_gamestart:
os.rename(self.gamestart_script_path+".orig", self.gamestart_script_path)
def create_movie(self):
file_id = 1
company = None
game_files = self.get_save_game()
if len(game_files) == 0:
print "No game files found for %s" % self.args.company
exit(1)
self.backup_autoexec = self.backup_script(self.autoexec_script_path)
self.backup_gamestart = self.backup_script(self.gamestart_script_path)
for game_file in game_files:
game=os.path.basename(game_file)
if company is None:
company = game.split(",")[0].strip()
screenshot_name="timelapse_%05d" % file_id
screenshot_name_ext = screenshot_name+".png"
self.make_screenshot_script(screenshot_name)
os.system(self.openttd+" -x -g \""+game+"\"")
os.unlink(self.gamestart_script_path)
im = Image.open(os.path.join(self.screenshot_path,screenshot_name_ext))
crop_info = self.get_crop_info(im)
frame_name = "frame_%05d.png" % file_id
final_frame = os.path.join(self.screenshot_path,frame_name)
im1 = im.crop(crop_info).convert('RGB').resize((self.args.width,self.args.height),Image.BILINEAR)
if self.args.timestamp:
im1 = self.draw_date(im1, game_file)
if self.args.check:
final_frame="timelapse_check.png"
im1.convert('P', palette=Image.ADAPTIVE, colors=255).save(final_frame)
im = im1 = None
file_id+=1
if self.args.check:
break
if not self.args.debug:
os.unlink(os.path.join(self.screenshot_path,screenshot_name_ext))
if not self.args.check:
ffmpeg_cmd = "%s -y -r 1 -i %s/frame_%%05d.png -r 24 -s %dx%d -c:v libx264 -an -vsync cfr \"%s.mp4\"" % (self.ffmpeg, self.screenshot_path, self.args.width, self.args.height, company)
os.system(ffmpeg_cmd)
self.clean()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Create a Timelapse from an OpenTTD game')
parser.add_argument('-c','--company', dest='company', required=True, help='Game Name')
parser.add_argument('-d','--dir', dest='ottd', metavar='OPENTTD_FOLDER', default='~/.openttd', help='OpenTTD folder (default %(default)s)')
parser.add_argument('-dx','--width', dest='width', type=int, default=1920, help='Width of frames (px) (default: %(default)s)')
parser.add_argument('-dy','--height', dest='height', type=int, default=1080, help='Height of frames (px) (default: %(default)s)')
parser.add_argument('-z','--zoom', dest='zoom', type=int, default=2, choices=xrange(1,5), help='Scale factor')
parser.add_argument('-l','--left', dest='left', type=int, default=50, help='Position from left in percent (default: %(default)s)')
parser.add_argument('-t','--top', dest='top', type=int, default=50, help='Position from top in percent (default: %(default)s)')
parser.add_argument('-s','--timestamp', dest='timestamp', action="store_true", help='Display year on frames')
parser.add_argument('-f','--font', dest='font_path', metavar="FONT PATH", default='/usr/share/fonts/truetype/msttcorefonts/Arial.ttf', help="Path to TTF file")
parser.add_argument('--debug', dest='debug', action="store_true", help='Do not clean temp files')
parser.add_argument('--check', dest='check', action="store_true", help='Output first image only, useful to verify the selected area')
args = parser.parse_args()
ottd = OpenTTDTimelapse(args)
ottd.create_movie()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment