Genre- and collection-specific thumbnails.
authorRadek Czajka <rczajka@rczajka.pl>
Mon, 6 Jul 2020 10:07:48 +0000 (12:07 +0200)
committerRadek Czajka <rczajka@rczajka.pl>
Mon, 6 Jul 2020 10:07:48 +0000 (12:07 +0200)
12 files changed:
src/archive/migrations/0018_auto_20200703_1718.py [new file with mode: 0644]
src/archive/templates/archive/file_managed.html
src/archive/views.py
src/youtube/admin.py
src/youtube/migrations/0011_thumbnailtemplate.py [new file with mode: 0644]
src/youtube/migrations/0012_move_thumbnail_definitions.py [new file with mode: 0644]
src/youtube/migrations/0013_auto_20200706_1123.py [new file with mode: 0644]
src/youtube/models.py
src/youtube/templates/youtube/preview.html
src/youtube/thumbnail.py
src/youtube/urls.py
src/youtube/views.py

diff --git a/src/archive/migrations/0018_auto_20200703_1718.py b/src/archive/migrations/0018_auto_20200703_1718.py
new file mode 100644 (file)
index 0000000..2bdb10b
--- /dev/null
@@ -0,0 +1,28 @@
+# Generated by Django 3.0.6 on 2020-07-03 17:18
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('archive', '0017_auto_20200603_0011'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='audiobook',
+            name='mp3_status',
+            field=models.SmallIntegerField(choices=[(1, 'Waiting'), (10, 'Queued'), (20, 'Encoding'), (30, 'Tagging'), (40, 'Converting audio'), (50, 'Converting video'), (60, 'Assembling audio'), (70, 'Assembling video'), (80, 'Joining audio and video'), (100, 'Sending'), (110, 'Setting thumbnail')], editable=False, null=True),
+        ),
+        migrations.AlterField(
+            model_name='audiobook',
+            name='ogg_status',
+            field=models.SmallIntegerField(choices=[(1, 'Waiting'), (10, 'Queued'), (20, 'Encoding'), (30, 'Tagging'), (40, 'Converting audio'), (50, 'Converting video'), (60, 'Assembling audio'), (70, 'Assembling video'), (80, 'Joining audio and video'), (100, 'Sending'), (110, 'Setting thumbnail')], editable=False, null=True),
+        ),
+        migrations.AlterField(
+            model_name='audiobook',
+            name='youtube_status',
+            field=models.SmallIntegerField(choices=[(1, 'Waiting'), (10, 'Queued'), (20, 'Encoding'), (30, 'Tagging'), (40, 'Converting audio'), (50, 'Converting video'), (60, 'Assembling audio'), (70, 'Assembling video'), (80, 'Joining audio and video'), (100, 'Sending'), (110, 'Setting thumbnail')], editable=False, null=True),
+        ),
+    ]
index ecb6292..d6e8be8 100644 (file)
   <div class="mt-4 alert alert-warning">{{ alert }}</div>
 {% endfor %}
 
-<div class="card mt-4">
-  <div class="card-header">
-    <h2>{% trans "Publishing" %}</h2>
+
+
+<div class="row mt-4">
+  <div class="col-xl-7">
+    <div class="card mt-4">
+      <div class="card-header">
+        <h2><a href="{% url 'book' audiobook.slug %}" %}">{{ audiobook.title }}</a></h2>
+      </div>
+      <div class="card-body">
+        <form method='post' action='.'>
+          {% csrf_token %}
+         {% bootstrap_form form %}
+          <input class="btn btn-primary" type="submit" value='{% trans "Commit" %}' />
+        </form>
+
+      </div>
+    </div>
   </div>
-  <div class="card-body">
+
+  <div class="col-xl-5">
     {% if audiobook.mp3_status or audiobook.ogg_status or audiobook.youtube_status %}
       <h2>{% trans "Publishing pending" %}</h2>
       <form method="post" action="{% url 'cancel_publishing' audiobook.id %}">
         {% csrf_token %}
         <input class='btn btn-danger' type="submit" value="{% trans "Cancel publishing" %}" />
       </form>
-
-      {% if audiobook.mp3_status %}
-        <hr/>
-        <h2>MP3</h2>
-
-        {% tags_table audiobook.get_mp3_tags.tags %}
-
-        <p>Status: <b>{{ audiobook.get_mp3_status_display }}</b></p>
-      {% endif %}
-
-      {% if audiobook.ogg_status %}
-        <hr/>
-        <h2>Ogg Vorbis</h2>
-
-        {% tags_table audiobook.get_ogg_tags.tags %}
-
-        <p>Status: <b>{{ audiobook.get_ogg_status_display }}</b></p>
-      {% endif %}
-
-      {% if audiobook.youtube_status %}
-        <hr/>
-        <h2>YouTube</h2>
-
-        <p>Status: <b>{{ audiobook.get_youtube_status_display }}</b></p>
-      {% endif %}
-
-    {% else %}
-
-      <table class='table'>
-        {% tags_table audiobook.new_publish_tags 0 %}
-        <tr><th></th><td>
-
-          <div class="row">
-            <div class="col-md-6">
-              {% if user_can_publish %}
-                <form method="post" action="{% url 'publish' audiobook.id %}">
-                  {% csrf_token %}
-                  <input class="btn btn-primary" type="submit" value="{% trans "Publish" %}" />
-                </form>
-              {% else %}
-                <a class="btn btn-primary" href="{% url 'apiclient_oauth' %}">Podłącz się</a>
-              {% endif %}
-
-              {% if not audiobook.mp3_published or not audiobook.ogg_published %}
-                <form class="mt-3" method="post" action="{% url 'convert' audiobook.id %}">
-                  {% csrf_token %}
-                  <input class="btn btn-secondary" type="submit" value="{% trans "Convert without publishing" %}" />
-                </form>
-              {% endif %}
-            </div>
-            <div class="col-md-6">
-              {% if audiobook.is_youtube_publishable %}
-              <p>
-               <a href="{% url 'youtube_preview' audiobook.id %}">
-                  {% trans "Preview YouTube metadata" %}
-                </a>
-              </p>
-              <form method="post" action="{% url 'youtube_publish' audiobook.id %}">
-                {% csrf_token %}
-                <input class="btn btn-primary" type="submit" value="{% trans "Publish on YouTube" %}" />
-              </form>
-              <br>
-              {% endif %}
-              {% if audiobook.youtube_id %}
-                <form method="post" action="{% url 'youtube_update' audiobook.id %}">
-                  {% csrf_token %}
-                  <input class="btn btn-secondary" type="submit" value="{% trans "Update YouTube metadata" %}">
-                </form>
-              {% endif %}
-
-            </div>
-          </div>
-        </td></tr>
-      </table>
     {% endif %}
-  </div>
-</div>
 
 
-<div class="row">
-  <div class="col-xl-4">
     <div class="card mt-4">
       <div class="card-header">
-       <h2>{% trans "MP3 file" %}</h2>
+        <h3>MP3, Ogg</h3>
       </div>
       <div class="card-body">
+        {% if audiobook.mp3_status %}
+          <hr/>
+          <h2>MP3</h2>
+
+          {% tags_table audiobook.get_mp3_tags.tags %}
+
+          <p>Status: <b>{{ audiobook.get_mp3_status_display }}</b></p>
+        {% endif %}
+
+        {% if audiobook.ogg_status %}
+          <hr/>
+          <h2>Ogg Vorbis</h2>
+
+          {% tags_table audiobook.get_ogg_tags.tags %}
+
+          <p>Status: <b>{{ audiobook.get_ogg_status_display }}</b></p>
+        {% endif %}
+
+
         {% if audiobook.mp3_file %}
           <p><a href="{% url 'download' audiobook.id 'mp3' %}">{% trans "Download MP3 file." %}</a></p>
           {% if audiobook.mp3_published %}
         {% else %}
           <p>{% trans "MP3 file hasn't been generated yet." %}</p>
         {% endif %}
-      </div>
-    </div>
-  </div>
 
-  <div class="col-xl-4">
-    <div class="card mt-4">
-      <div class="card-header">
-        <h2>{% trans "Ogg Vorbis file" %}</h2>
-      </div>
-      <div class="card-body">
         {% if audiobook.ogg_file %}
           <p><a href="{% url 'download' audiobook.id 'ogg' %}">{% trans "Download Ogg Vorbis file." %}</a></p>
           {% if audiobook.ogg_published %}
         {% else %}
           <p>{% trans "Ogg Vorbis file hasn't been generated yet." %}</p>
         {% endif %}
+
+        {% if user_can_publish %}
+          <form method="post" action="{% url 'publish' audiobook.id %}">
+            {% csrf_token %}
+            <input class="btn btn-primary" type="submit" value="{% trans "Publish" %}" />
+          </form>
+        {% else %}
+          <a class="btn btn-primary" href="{% url 'apiclient_oauth' %}">Podłącz się</a>
+        {% endif %}
+
+        {% if not audiobook.mp3_published or not audiobook.ogg_published %}
+          <form class="mt-3" method="post" action="{% url 'convert' audiobook.id %}">
+            {% csrf_token %}
+            <input class="btn btn-secondary" type="submit" value="{% trans "Convert without publishing" %}" />
+          </form>
+        {% endif %}
+
+
       </div>
     </div>
-  </div>
 
-  <div class="col-xl-4">
     <div class="card mt-4">
       <div class="card-header">
-        <h2>{% trans "YouTube" %}</h2>
+        <h3>YouTube</h3>
       </div>
       <div class="card-body">
+        {% if audiobook.youtube_status %}
+          <hr/>
+          <h2>YouTube</h2>
+
+          <p>Status: <b>{{ audiobook.get_youtube_status_display }}</b></p>
+        {% endif %}
+
         {% if audiobook.youtube_id %}
           {% if audiobook.youtube_id %}
             <p>
-              <a href="https://youtu.be/{{ audiobook.youtube_id }}" target="_blank">{% trans "See on YouTube" %}</a>
+              <a href="https://youtu.be/{{ audiobook.youtube_id }}" target="_blank" title="{% trans "See on YouTube" %}">
+                <img src="https://i.ytimg.com/vi/{{ audiobook.youtube_id }}/hq720.jpg" style="width: 100%">
+
+              </a>
             </p>
           {% endif %}
           {% if audiobook.youtube_published %}
         {% else %}
           <p>{% trans "YouTube file hasn't been generated yet." %}</p>
         {% endif %}
+
+        {% if audiobook.youtube_id %}
+          <form method="post" action="{% url 'youtube_update_thumbnail' audiobook.id %}">
+            {% csrf_token %}
+            <input class="btn btn-secondary" type="submit" value="{% trans "Update YouTube thumbnail" %}">
+          </form>
+          <br>
+
+          <form method="post" action="{% url 'youtube_update' audiobook.id %}">
+            {% csrf_token %}
+            <input class="btn btn-secondary" type="submit" value="{% trans "Update YouTube metadata" %}">
+          </form>
+        {% endif %}
+
+        {% if audiobook.is_youtube_publishable %}
+          <br>
+          <p>
+           <a href="{% url 'youtube_preview' audiobook.id %}">
+              {% trans "Preview YouTube metadata" %}
+            </a>
+          </p>
+          <form method="post" action="{% url 'youtube_publish' audiobook.id %}">
+            {% csrf_token %}
+            <input class="btn btn-primary" type="submit" value="{% trans "Publish on YouTube" %}" />
+          </form>
+        {% endif %}
+
       </div>
     </div>
   </div>
 
+</div>
+
+
+
+<div class="row">
   <div class="col-xl-6">
+
     <div class="card mt-4">
       <div class="card-header">
-       <h2>Plik źródłowy</h2>
+        <h3>Metadane dla MP3, Ogg</h3>
       </div>
       <div class="card-body">
-        <p>Last modified: {{ audiobook.modified }}</p>
-        <p>Plik źródłowy: <a href='{% url "download" audiobook.id %}'>{{ path }}</a>
-          (sha1: <tt>{{ audiobook.source_sha1 }}</tt>).
-        </p>
-        {% multiple_tags_table tags %}
-
-        <form method="post" action="{% url 'remove_to_archive' audiobook.id %}"
-              onsubmit='return confirm("{% trans "Are you sure you want to move this audiobook to archive?" %}")'>
-          {% csrf_token %}
-          <input class="btn btn-danger" type="submit" value="{% trans "Remove to archive" %}" />
-        </form>
-
+        <table class='table'>
+          {% tags_table audiobook.new_publish_tags 0 %}
+        </table>
       </div>
     </div>
-  </div>
 
+  </div>
   <div class="col-xl-6">
-    <div class="card mt-4 mb-4">
+    <div class="card mt-4">
       <div class="card-header">
-       <h2>{% trans "Update tags" %}</h2>
+        <h3>Metadane dla YouTube</h3>
       </div>
       <div class="card-body">
-
-        <form method='post' action='.'>
-          {% csrf_token %}
-         {% bootstrap_form form %}
-          <input class="btn btn-primary" type="submit" value='{% trans "Commit" %}' />
-        </form>
-
+        <img src="{% url 'youtube_thumbnail' audiobook.id %}" style="width:100%">
+        <strong>{{ youtube_title }}</strong><br><br>
+        {{ youtube_description|linebreaksbr }}
       </div>
     </div>
   </div>
+
+</div>
+
+
+<div class="card mt-4 mb-4">
+  <div class="card-header">
+    <h2>Plik źródłowy</h2>
+  </div>
+  <div class="card-body">
+    <p>Last modified: {{ audiobook.modified }}</p>
+    <p>Plik źródłowy: <a href='{% url "download" audiobook.id %}'>{{ path }}</a>
+      (sha1: <tt>{{ audiobook.source_sha1 }}</tt>).
+    </p>
+    {% multiple_tags_table tags %}
+
+    <form method="post" action="{% url 'remove_to_archive' audiobook.id %}"
+          onsubmit='return confirm("{% trans "Are you sure you want to move this audiobook to archive?" %}")'>
+      {% csrf_token %}
+      <input class="btn btn-danger" type="submit" value="{% trans "Remove to archive" %}" />
+    </form>
+
+  </div>
 </div>
 
 {% endblock %}
index f1b1c1b..2170aff 100644 (file)
@@ -244,12 +244,14 @@ def file_managed(request, id):
             except IOError:
                 raise Http404
 
-    path = audiobook.source_file.path[len(settings.FILES_PATH):].lstrip('/')
-
-    # for tags update
-    tags = mutagen.File(audiobook.source_file.path.encode('utf-8'))
-    if not tags:
-        tags = {}
+    tags = {}
+    if audiobook.source_file:
+        path = audiobook.source_file.path[len(settings.FILES_PATH):].lstrip('/')
+
+        # for tags update
+        tags = mutagen.File(audiobook.source_file.path.encode('utf-8'))
+        if not tags:
+            tags = {}
     form = AudiobookForm(instance=audiobook)
 
     user_can_publish = (
@@ -265,6 +267,12 @@ def file_managed(request, id):
         if set(series.values_list('index', flat=True)) != set(range(1, parts_count + 1)):
             alerts.append(_('Part indexes are not 1..%(parts_count)d.') % {"parts_count": parts_count})
 
+    from youtube.models import YouTube
+    youtube = YouTube.objects.first()
+    youtube_title = youtube.get_title(audiobook)
+    youtube_description = youtube.get_description(audiobook)
+
+            
     return render(request, "archive/file_managed.html", locals())
 
 
index 2edc745..3c36eee 100644 (file)
@@ -14,3 +14,9 @@ admin.site.register(models.YouTube, YouTubeAdmin)
 
 
 admin.site.register(models.Font)
+
+
+class ThumbnailTemplateAdmin(admin.ModelAdmin):
+    list_display = ['order', 'genres', 'collections']
+
+admin.site.register(models.ThumbnailTemplate, ThumbnailTemplateAdmin)
diff --git a/src/youtube/migrations/0011_thumbnailtemplate.py b/src/youtube/migrations/0011_thumbnailtemplate.py
new file mode 100644 (file)
index 0000000..1f9999b
--- /dev/null
@@ -0,0 +1,28 @@
+# Generated by Django 3.0.6 on 2020-07-03 17:18
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('youtube', '0010_auto_20200520_1353'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ThumbnailTemplate',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('order', models.SmallIntegerField()),
+                ('is_active', models.BooleanField()),
+                ('background', models.FileField(upload_to='youtube/thumbnail')),
+                ('definition', models.TextField()),
+                ('genres', models.CharField(blank=True, max_length=255)),
+                ('collections', models.CharField(blank=True, max_length=255)),
+            ],
+            options={
+                'ordering': ('order',),
+            },
+        ),
+    ]
diff --git a/src/youtube/migrations/0012_move_thumbnail_definitions.py b/src/youtube/migrations/0012_move_thumbnail_definitions.py
new file mode 100644 (file)
index 0000000..a54cf12
--- /dev/null
@@ -0,0 +1,52 @@
+# Generated by Django 3.0.6 on 2020-07-03 17:18
+
+from django.db import migrations
+import yaml
+
+
+def generate_versions(version_lists):
+    if not version_lists:
+        yield []
+    else:
+        for subversion in generate_versions(version_lists[1:]):
+            for version in version_lists[0]:
+                yield [version] + subversion
+
+
+def move_definitions(apps, schema_editor):
+    YouTube = apps.get_model('youtube', 'YouTube')
+    ThumbnailTemplate = apps.get_model('youtube', 'ThumbnailTemplate')
+    order = 1
+    for youtube in YouTube.objects.all():
+        src_def = yaml.load(youtube.thumbnail_definition)
+        version_lists = [
+            box['versions']
+            for box in src_def['boxes']
+        ]
+        for version in generate_versions(version_lists):
+            defn = yaml.dump(
+                {"boxes": version},
+                allow_unicode=True,
+                default_flow_style=False
+            )
+            ThumbnailTemplate.objects.create(
+                order=order,
+                is_active=True,
+                background=youtube.thumbnail_template,
+                definition=defn,
+            )
+            order += 1
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('youtube', '0011_thumbnailtemplate'),
+    ]
+
+    operations = [
+        migrations.RunPython(
+            move_definitions,
+            migrations.RunPython.noop
+        )
+    ]
diff --git a/src/youtube/migrations/0013_auto_20200706_1123.py b/src/youtube/migrations/0013_auto_20200706_1123.py
new file mode 100644 (file)
index 0000000..74485df
--- /dev/null
@@ -0,0 +1,21 @@
+# Generated by Django 3.0.6 on 2020-07-06 11:23
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('youtube', '0012_move_thumbnail_definitions'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='youtube',
+            name='thumbnail_definition',
+        ),
+        migrations.RemoveField(
+            model_name='youtube',
+            name='thumbnail_template',
+        ),
+    ]
index 5fe3c2b..219a682 100644 (file)
@@ -4,6 +4,7 @@ from tempfile import NamedTemporaryFile
 from django.db import models
 from django.utils.translation import gettext_lazy as _
 from django.template import Template, Context
+import requests
 from apiclient import youtube_call
 from .utils import (
     concat_audio,
@@ -32,8 +33,6 @@ class YouTube(models.Model):
     outro_flac = models.FileField(upload_to='youtube/outro_flac', blank=True)
     loop_card = models.FileField(upload_to='youtube/card', blank=True)
     loop_video = models.FileField(upload_to='youtube/loop_video', blank=True)
-    thumbnail_template = models.FileField(upload_to='youtube/thumbnail', blank=True)
-    thumbnail_definition = models.TextField(blank=True)
     privacy_status = models.CharField(max_length=16, choices=[
         ('public', _('public')),
         ('unlisted', _('unlisted')),
@@ -177,27 +176,21 @@ class YouTube(models.Model):
 
     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},
-            data=thumbnail.getvalue(),
-        )
+        if thumbnail is not None:
+            response = youtube_call(
+                "POST",
+                "https://www.googleapis.com/upload/youtube/v3/thumbnails/set",
+                params={'videoId': audiobook.youtube_id},
+                data=thumbnail.getvalue(),
+            )
 
     def prepare_thumbnail(self, audiobook):
-        img = create_thumbnail(
-            self.thumbnail_template.path,
-            self.thumbnail_definition,
-            {
-                "author": ', '.join((a['name'] for a in audiobook.book['authors'])),
-                "title": audiobook.book['title'],
-                "part": (audiobook.youtube_volume or audiobook.part_name).strip(),
-            },
-            lambda name: Font.objects.get(name=name).truetype.path
-        )
-        buf = io.BytesIO()
-        img.save(buf, format='PNG')
-        return buf
+        for thumbnail_template in ThumbnailTemplate.objects.filter(is_active=True).order_by('order'):
+            if not thumbnail_template.is_for_audiobook(audiobook):
+                continue
+            thumbnail = thumbnail_template.generate(audiobook)
+            if thumbnail is not None:
+                return thumbnail
 
 
 class Card(models.Model):
@@ -216,3 +209,60 @@ class Font(models.Model):
 
     def __str__(self):
         return self.name
+
+
+class ThumbnailTemplate(models.Model):
+    order = models.SmallIntegerField()
+    is_active = models.BooleanField()
+    background = models.FileField(upload_to='youtube/thumbnail')
+    definition = models.TextField()
+    genres = models.CharField(max_length=255, blank=True)
+    collections = models.CharField(max_length=255, blank=True)
+
+    class Meta:
+        ordering = ('order', )
+
+    def generate(self, audiobook):
+        try:
+            img = create_thumbnail(
+                self.background.path,
+                self.definition,
+                {
+                    "author": ', '.join((a['name'] for a in audiobook.book['authors'])),
+                    "title": audiobook.book['title'],
+                    "part": (audiobook.youtube_volume or audiobook.part_name).strip(),
+                },
+                lambda name: Font.objects.get(name=name).truetype.path
+            )
+        except Exception as e:
+            print(e)
+            return
+        else:
+            buf = io.BytesIO()
+            img.save(buf, format='PNG')
+            return buf
+
+    def is_for_audiobook(self, audiobook):
+        if self.genres:
+            book_genres = set([g['slug'] for g in audiobook.book['genres']])
+            template_genres = set([g.strip() for g in self.genres.split(',')])
+            if not book_genres.intersection(template_genres):
+                return False
+
+        if self.collections:
+            template_collections = set([g.strip() for g in self.collections.split(',')])
+            in_any = False
+            for collection in template_collections:
+                apidata = requests.get(
+                    f'https://wolnelektury.pl/api/collections/{collection}/'
+                ).json()
+                for book in apidata['books']:
+                    if book['slug'] == audiobook.slug:
+                        in_any = True
+                        break
+                if in_any:
+                    break
+            if not in_any:
+                return False
+        
+        return True
index c017265..9dee0ee 100644 (file)
     </div>
     <div class="card-body">
       <img src="{% url 'youtube_thumbnail' object.id %}" style="width:100%">
-    </div>
+      <div class="row">
+        {% for t in templates %}
+          <div class="col-md-2">
+            <a href="{% url 'youtube_thumbnail' object.id t.id %}">
+              <img src="{% url 'youtube_thumbnail' object.id t.id %}" style="width:100%">
+            </a>
+          </div>
+        {% endfor %}
+      </div>
+      </div>
+      </div>
   </div>
 
-
   <div class="card mt-4 mb-4">
     <div class="card-header">
       <h2>{{ title }}</h2>
index 42e8070..8eb7973 100644 (file)
@@ -2,12 +2,6 @@ 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 = ''
@@ -28,8 +22,7 @@ def split_to_lines(text, draw, font, max_width):
         yield current
 
         
-def draw_version(img, d, context, get_font_path):
-    # todo: do this in a subimg
+def draw_box(img, d, context, get_font_path):
     newimg = Image.new(
         'RGBA',
         (
@@ -48,7 +41,11 @@ def draw_version(img, d, context, get_font_path):
             continue
         if item.get('uppercase'):
             text = text.upper()
-        font = ImageFont.truetype(get_font_path(item['font-family']), item['font-size'])
+        font = ImageFont.truetype(
+            get_font_path(item['font-family']),
+            item['font-size'],
+            layout_engine=ImageFont.LAYOUT_BASIC
+        )
         max_width = item.get('max-width', newimg.size[0])
 
         for line in split_to_lines(text, draw, font, max_width):
@@ -66,5 +63,6 @@ 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)
+        if not draw_box(img, boxdef, context, get_font_path):
+            raise ValueError()
     return img
index fb9ddc7..9de274e 100644 (file)
@@ -5,6 +5,8 @@ from . import views
 urlpatterns = [
     url(r'^publish/(\d+)/$', views.publish, name="youtube_publish"),
     path('thumbnail/<int:aid>/', views.thumbnail, name='youtube_thumbnail'),
+    path('thumbnail/<int:aid>/<int:thumbnail_id>/', views.thumbnail, name='youtube_thumbnail'),
     path('preview/<int:pk>/', views.Preview.as_view(), name="youtube_preview"),
     path('update/<int:pk>/', views.Update.as_view(), name="youtube_update"),
+    path('update-thumbnail/<int:pk>/', views.UpdateThumbnail.as_view(), name="youtube_update_thumbnail"),
 ]
index 4c002ee..058178f 100644 (file)
@@ -25,11 +25,16 @@ def publish(request, aid, publish=True):
     return redirect(reverse('file', args=[aid]))
 
 
-def thumbnail(request, aid):
+def thumbnail(request, aid, thumbnail_id=None):
     audiobook = get_object_or_404(Audiobook, id=aid)
-    yt = models.YouTube.objects.first()
-    buf = yt.prepare_thumbnail(audiobook)
-    return HttpResponse(buf.getvalue(), content_type='image/png')
+    if thumbnail_id is None:
+        yt = models.YouTube.objects.first()
+        buf = yt.prepare_thumbnail(audiobook)
+    else:
+        template = get_object_or_404(models.ThumbnailTemplate, id=thumbnail_id)
+        buf = template.generate(audiobook)
+    buf = buf.getvalue() if buf is not None else b''
+    return HttpResponse(buf, content_type='image/png')
 
 
 class Preview(DetailView):
@@ -42,6 +47,7 @@ class Preview(DetailView):
         ctx['data'] = yt.get_data(ctx['object'])
         ctx['title'] = yt.get_title(ctx['object'])
         ctx['description'] = yt.get_description(ctx['object'])
+        ctx['templates'] = models.ThumbnailTemplate.objects.all()
         return ctx
 
 
@@ -53,3 +59,13 @@ class Update(SingleObjectMixin, View):
         yt = models.YouTube.objects.first()
         yt.update_data(self.get_object())
         return redirect(reverse('file', args=[pk]))
+
+
+@method_decorator(permission_required('archive.change_audiobook'), name='dispatch')
+class UpdateThumbnail(SingleObjectMixin, View):
+    model = Audiobook
+
+    def post(self, request, pk):
+        yt = models.YouTube.objects.first()
+        yt.update_thumbnail(self.get_object())
+        return redirect(reverse('file', args=[pk]))