pandora_cms/scripts/poster.cms.py

208 lines
7.4 KiB
Python
Raw Permalink Normal View History

2018-05-08 11:39:59 +00:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from __future__ import division
import os
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 render_poster(data, poster):
title = ox.decode_html(data.get('title', '')).upper()
director = ox.decode_html(u', '.join(data.get('director', []))).upper()
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')
oxdb_id = data['oxdbId']
2018-05-09 09:11:31 +00:00
id = data.get('imdbId', data['id'])
2018-05-08 11:39:59 +00:00
frame = data.get('frame')
timeline = data.get('timeline')
poster_size = (640, 1024)
frame_size = (640, 480)
frame_ratio = frame_size[0] / frame_size[1]
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')
hue = int(oxdb_id[2:10], 16) / pow(2, 32) * 360
image_color = getRGB([hue, 1, 0.2])
background_color = getRGB([hue, 1, 0.4])
foreground_color = getRGB([hue, 1, 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)
2018-08-07 09:55:53 +00:00
small_frame_image = get_frame(data['id'], 96, round(position * 25) / 25)
2018-05-08 11:39:59 +00:00
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
# year
if year:
drawText(
poster_image,
(margin, poster_size[1] - timeline_size[1] - margin - font_size_large),
year,
font_file,
font_size_large,
foreground_color
)
# id
drawText(
poster_image,
(
poster_size[0] - margin - getTextSize(poster_image, id, font_file, font_size_small)[0],
poster_size[1] - timeline_size[1] - margin - font_size_small
),
id,
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()