Thumbnail generator.
authorRadek Czajka <rczajka@rczajka.pl>
Fri, 15 May 2020 15:10:59 +0000 (17:10 +0200)
committerRadek Czajka <rczajka@rczajka.pl>
Fri, 15 May 2020 15:10:59 +0000 (17:10 +0200)
src/youtube/models.py
src/youtube/thumbnail.py [new file with mode: 0644]

index 30b3006..08b2d18 100644 (file)
@@ -1,3 +1,4 @@
+import io
 from os import unlink
 from tempfile import NamedTemporaryFile
 from django.db import models
 from os import unlink
 from tempfile import NamedTemporaryFile
 from django.db import models
@@ -12,6 +13,7 @@ from .utils import (
     get_framerate,
     mux,
 )
     get_framerate,
     mux,
 )
+from .thumbnail import create_thumbnail
 
 
 class YouTube(models.Model):
 
 
 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'])
         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):
         return response
 
     def prepare_file(self, input_path, output_path=None):
@@ -115,6 +119,26 @@ class YouTube(models.Model):
     # license
     # selfDeclaredMadeForKids
 
     # 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)
 
 class Card(models.Model):
     youtube = models.ForeignKey(YouTube, models.CASCADE)
@@ -124,3 +148,11 @@ class Card(models.Model):
 
     class Meta:
         ordering = ('order', )
 
     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 (file)
index 0000000..3f67cac
--- /dev/null
@@ -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