pandora_ninetytwo/scripts/poster.py

227 lines
7.8 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from __future__ import division
import os
import hashlib
from PIL import Image
from PIL import ImageDraw
import json
from optparse import OptionParser
import ox
from ox.image import drawText, getRGB, getTextSize, wrapText
import subprocess
import sys
root_dir = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
static_root = os.path.join(os.path.dirname(__file__), 'data')
def get_frame(id, height, position):
cmd = [
os.path.join(root_dir, 'pandora', 'manage.py'), 'get_frame',
str(id), str(height), "%f" % (round(position * 25) / 25)
]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
output = p.communicate()
frame_path = output[0].strip()
if frame_path:
frame_image = Image.open(frame_path)
return frame_image
def get_hue(title):
return ox.getHSL(
[int(''.join(x), 16) for x in zip(
hashlib.sha1(title.encode('utf-8')).hexdigest()[:6]
)]
)[0]
def render_poster(data, poster):
title = ox.decode_html(data.get('title', '')).upper()
for key in [
'director', 'cinematographer', 'editor',
'writer', 'producer', 'featuring'
]:
director = ox.decode_html(u', '.join(data.get(key, []))).upper()
if director:
break
for key, value in {u'\u03a0': 'PI', u'ß': u'SS'}.items():
title = title.replace(key, value)
director = director.replace(key, value)
year = str(data.get('year', ''))
duration = data.get('duration')
id = data['id']
frame = data.get('frame')
timeline = data.get('timeline')
poster_size = (640, 1024)
frame_size = (640, 480)
frame_ratio = frame_size[0] / frame_size[1]
logo_size = (32, 32)
small_frames = 8
small_frame_size = (80, 64)
small_frame_ratio = small_frame_size[0] / small_frame_size[1]
timeline_size = (640, 32)
margin = 16
font_size_small = 32
font_size_large = 48
font_file = os.path.join(static_root, 'MontserratBold.ttf')
if title:
hue = get_hue(title)
saturation = 1
else:
hue = 0
saturation = 0
image_color = getRGB([hue, saturation, 0.2])
background_color = getRGB([hue, saturation, 0.4])
foreground_color = getRGB([hue, saturation, 0.8])
poster_image = Image.new('RGB', poster_size)
draw = ImageDraw.Draw(poster_image)
# frame
if frame:
frame_image = Image.open(frame)
frame_image_ratio = frame_image.size[0] / frame_image.size[1]
if frame_ratio < frame_image_ratio:
frame_image = frame_image.resize((int(frame_size[1] * frame_image_ratio), frame_size[1]), Image.ANTIALIAS)
left = int((frame_image.size[0] - frame_size[0]) / 2)
frame_image = frame_image.crop((left, 0, left + frame_size[0], frame_size[1]))
else:
frame_image = frame_image.resize((frame_size[0], int(frame_size[0] / frame_image_ratio)), Image.ANTIALIAS)
top = int((frame_image.size[1] - frame_size[1]) / 2)
frame_image = frame_image.crop((0, top, frame_size[0], top + frame_size[1]))
poster_image.paste(frame_image, (0, 0))
else:
draw.rectangle(((0, 0), frame_size), fill=image_color)
# small frames
if duration:
for i in range(small_frames):
position = duration * (i + 1) / (small_frames + 1)
small_frame_image = get_frame(id, 96, round(position * 25) / 25)
if small_frame_image:
small_frame_image_ratio = small_frame_image.size[0] / small_frame_image.size[1]
if small_frame_ratio < small_frame_image_ratio:
small_frame_image = small_frame_image.resize((int(small_frame_size[1] * small_frame_image_ratio), small_frame_size[1]), Image.ANTIALIAS)
left = int((small_frame_image.size[0] - small_frame_size[0]) / 2)
small_frame_image = small_frame_image.crop((left, 0, left + small_frame_size[0], small_frame_size[1]))
else:
small_frame_image = small_frame_image.resize((small_frame_size[0], int(small_frame_size[0] / small_frame_image_ratio)), Image.ANTIALIAS)
top = int((small_frame_image.size[1] - small_frame_size[1]) / 2)
small_frame_image = small_frame_image.crop((0, top, small_frame_size[0], top + small_frame_size[1]))
poster_image.paste(small_frame_image, (i * small_frame_size[0], frame_size[1]))
else:
draw.rectangle(((0, frame_size[1]), (poster_size[0], frame_size[1] + small_frame_size[1])), fill=image_color)
# text
draw.rectangle(((0, frame_size[1] + small_frame_size[1]), (poster_size[0], poster_size[1] - timeline_size[1])), fill=background_color)
offset_top = frame_size[1] + small_frame_size[1] + margin - 8
text_height = poster_size[1] - frame_size[1] - small_frame_size[1] - 3 * margin - font_size_large - timeline_size[1]
if not director:
title_max_lines = int(text_height / font_size_large)
else:
title_max_lines = min(len(wrapText(
title,
poster_size[0] - 2 * margin,
0,
font_file,
font_size_large
)), int((text_height - margin - font_size_small - 8) / font_size_large))
director_max_lines = int((text_height - title_max_lines * font_size_large) / font_size_small)
# title
lines = wrapText(
title,
poster_size[0] - 2 * margin,
title_max_lines,
font_file,
font_size_large
)
for line in lines:
drawText(
poster_image,
(margin, offset_top),
line,
font_file,
font_size_large,
foreground_color
)
offset_top += font_size_large
offset_top += margin
# director
if director:
lines = wrapText(
director,
poster_size[0] - 2 * margin,
director_max_lines,
font_file,
font_size_small
)
for line in lines:
drawText(
poster_image,
(margin, offset_top),
line,
font_file,
font_size_small,
foreground_color
)
offset_top += font_size_small
# id
drawText(
poster_image,
(margin, poster_size[1] - timeline_size[1] - margin - font_size_large),
id,
font_file,
font_size_large,
foreground_color
)
# year
if year:
drawText(
poster_image,
(
poster_size[0] - margin - getTextSize(poster_image, year, font_file, font_size_small)[0],
poster_size[1] - timeline_size[1] - margin - font_size_small
),
year,
font_file,
font_size_small,
foreground_color
)
# timeline
if timeline:
timeline_image = Image.open(timeline)
timeline_image = timeline_image.resize(timeline_size, Image.ANTIALIAS)
poster_image.paste(timeline_image, (0, poster_size[1] - timeline_size[1]))
else:
draw.rectangle(((0, poster_size[1] - timeline_size[1]), poster_size), fill=image_color)
poster_image.save(poster)
def main():
parser = OptionParser()
parser.add_option('-d', '--data', dest='data', help='json file with metadata', default=None)
parser.add_option('-p', '--poster', dest='poster', help='Poster (image file to be written)')
(options, args) = parser.parse_args()
if None in (options.data, options.poster):
parser.print_help()
sys.exit()
if options.data == '-':
data = json.load(sys.stdin)
else:
with open(options.data) as f:
data = json.load(f)
render_poster(data, options.poster)
if __name__ == "__main__":
main()