Created
July 29, 2014 18:54
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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