From 4660a2368dbaa858636ca3ab8b64433b721b1457 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Fri, 15 May 2020 17:10:59 +0200 Subject: [PATCH] Thumbnail generator. --- src/youtube/models.py | 32 +++++++++++++++++++ src/youtube/thumbnail.py | 68 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/youtube/thumbnail.py diff --git a/src/youtube/models.py b/src/youtube/models.py index 30b3006..08b2d18 100644 --- a/src/youtube/models.py +++ b/src/youtube/models.py @@ -1,3 +1,4 @@ +import io from os import unlink from tempfile import NamedTemporaryFile from django.db import models @@ -12,6 +13,7 @@ from .utils import ( get_framerate, mux, ) +from .thumbnail import create_thumbnail class YouTube(models.Model): @@ -64,6 +66,8 @@ class YouTube(models.Model): data = response.json() audiobook.youtube_id = data['id'] audiobook.save(update_fields=['youtube_id']) + + self.update_thumbnail(audiobook) return response def prepare_file(self, input_path, output_path=None): @@ -115,6 +119,26 @@ class YouTube(models.Model): # license # selfDeclaredMadeForKids + def update_thumbnail(self, audiobook): + thumbnail = self.prepare_thumbnail(audiobook) + response = youtube_call( + "POST", + "https://www.googleapis.com/upload/youtube/v3/thumbnails/set", + params={'videoId': audiobook.youtube_id}, + media_data=buf.read(), # Or just data? + ) + + def prepare_thumbnail(self, audiobook): + img = create_thumbnail( + self.thumbnail_template.path, + self.thumbnail_definition, + {}, # TODO proper context + lambda name: Font.objects.get(name=name).truetype.path + ) + buf = io.BytesIO() + img.save(buf, format='PNG') + return buf + class Card(models.Model): youtube = models.ForeignKey(YouTube, models.CASCADE) @@ -124,3 +148,11 @@ class Card(models.Model): class Meta: ordering = ('order', ) + + +class Font(models.Model): + name = models.CharField(max_length=255, unique=True) + truetype = models.FileField(upload_to='youtube/font') + + def __str__(self): + return self.name diff --git a/src/youtube/thumbnail.py b/src/youtube/thumbnail.py new file mode 100644 index 0000000..3f67cac --- /dev/null +++ b/src/youtube/thumbnail.py @@ -0,0 +1,68 @@ +import yaml +from PIL import Image, ImageDraw, ImageFont + + +def drawbox(img, d, context, get_font_path): + for version in d['versions']: + if draw_version(img, version, context, get_font_path): + break + + +def split_to_lines(text, draw, font, max_width): + words = text.split() + current = '' + while words: + new_words = [] + new_words.append(words.pop(0)) + while len(new_words[-1]) == 1 and words: + new_words.append(words.pop(0)) + new_words = ' '.join(new_words) + new_line = ' '.join([current, new_words]).strip() + width = draw.textsize(new_line, font=font)[0] + if width > max_width: + yield current + current = new_words + else: + current = new_line + if current: + yield current + + +def draw_version(img, d, context, get_font_path): + # todo: do this in a subimg + newimg = Image.new( + 'RGBA', + ( + img.size[0] - d.get('x', 0), + d.get('max-height', img.size[1] - d.get('y', 0)), + ) + ) + + draw = ImageDraw.Draw(newimg) + cursor = 0 + for item in d['items']: + if item.get('vskip'): + cursor += item['vskip'] + text = item['text'].format(**context) + if item.get('uppercase'): + text = text.upper() + font = ImageFont.truetype(get_font_path(item['font-family']), item['font-size']) + max_width = item.get('max-width', newimg.size[0]) + + for line in split_to_lines(text, draw, font, max_width): + realheight = draw.textsize(line, font=font)[1] + if cursor + realheight > newimg.size[1]: + return False + print('x', draw.text((0, cursor), line, font=font, fill=item.get('color'))) + cursor += item['line-height'] + + img.paste(newimg, (d.get('x', 0), d.get('y', 0)), newimg) + return True + + +def create_thumbnail(background_path, defn, context, get_font_path): + img = Image.open(background_path) + d = yaml.load(defn) + for boxdef in d['boxes']: + drawbox(img, boxdef, context, get_font_path) + return img -- 2.20.1