Thumbnail generator.
[audio.git] / src / youtube / models.py
1 import io
2 from os import unlink
3 from tempfile import NamedTemporaryFile
4 from django.db import models
5 from django.utils.translation import gettext_lazy as _
6 from django.template import Template, Context
7 from apiclient import youtube_call
8 from .utils import (
9     video_from_image,
10     cut_video,
11     concat_videos,
12     get_duration,
13     get_framerate,
14     mux,
15 )
16 from .thumbnail import create_thumbnail
17
18
19 class YouTube(models.Model):
20     title_template = models.CharField(max_length=1024, blank=True)
21     description_template = models.TextField(blank=True)
22     category = models.IntegerField(null=True, blank=True)  # get categories
23     loop_card = models.FileField(upload_to='youtube/card', blank=True)
24     loop_video = models.FileField(upload_to='youtube/loop_video', blank=True)
25     thumbnail_template = models.FileField(upload_to='youtube/thumbnail', blank=True)
26     thumbnail_definition = models.TextField(blank=True)
27     genres = models.CharField(max_length=2048, blank=True)
28
29     class Meta:
30         verbose_name = _("YouTube configuration")
31         verbose_name_plural = _("YouTube configurations")
32
33     def publish(self, audiobook, path):
34         ctx = Context(dict(audiobook=audiobook))
35         description = Template(self.description_template).render(ctx)
36         title = Template(self.title_template).render(ctx)
37         privacy = 'private'
38
39         data = dict(
40             snippet=dict(
41                 title=title,
42                 description=description,
43                 # tags=tags,
44                 # categoryId=category,
45                 # defaultLanguage
46             ),
47             status=dict(
48                 privacyStatus=privacy,
49                 # license
50                 # selfDeclaredMadeForKids
51             ),
52             # recordingDetails=dict(
53             # recordingDate
54             # ),
55         )
56         part = ",".join(data.keys())
57
58         with open(path, "rb") as f:
59             response = youtube_call(
60                 "POST",
61                 "https://www.googleapis.com/upload/youtube/v3/videos",
62                 params={'part': part},
63                 data=data,
64                 media_data=f.read(),
65             )
66         data = response.json()
67         audiobook.youtube_id = data['id']
68         audiobook.save(update_fields=['youtube_id'])
69
70         self.update_thumbnail(audiobook)
71         return response
72
73     def prepare_file(self, input_path, output_path=None):
74         duration = get_duration(input_path)
75         video = self.prepare_video(duration)
76         output = mux([video, input_path], output_path=output_path)
77         unlink(video)
78         return output
79
80     def prepare_video(self, duration):
81         concat = []
82         outro = []
83         delete = []
84
85         if self.loop_video:
86             fps = get_framerate(self.loop_video.path)
87         else:
88             fps = 25
89
90         loop_duration = duration
91         for card in self.card_set.filter(order__lt=0, duration__gt=0):
92             loop_duration -= card.duration
93             card_video = video_from_image(
94                 card.image.path, card.duration, fps=fps
95             )
96             (concat if card.order < 0 else outro).append(card_video)
97             delete.append(intro)
98
99         if self.loop_video:
100             loop_video_duration = get_duration(self.loop_video.path)
101             times_loop = int(loop_duration // loop_video_duration)
102
103             leftover_duration = loop_duration % loop_video_duration
104             leftover = cut_video(self.loop_video.path, leftover_duration)
105             concat.extend([self.loop_video.path] * times_loop + [leftover])
106             delete.append(leftover)
107         else:
108             leftover = video_from_image(self.loop_card.path, loop_duration)
109             concat.append(video_from_image(self.loop_card.path, loop_duration, fps=fps))
110             delete.append(leftover)
111         concat.extend(outro)
112
113         output = concat_videos(concat)
114         for p in delete:
115             unlink(p)
116         return output
117
118     # tags
119     # license
120     # selfDeclaredMadeForKids
121
122     def update_thumbnail(self, audiobook):
123         thumbnail = self.prepare_thumbnail(audiobook)
124         response = youtube_call(
125             "POST",
126             "https://www.googleapis.com/upload/youtube/v3/thumbnails/set",
127             params={'videoId': audiobook.youtube_id},
128             media_data=buf.read(),  # Or just data?
129         )
130
131     def prepare_thumbnail(self, audiobook):
132         img = create_thumbnail(
133             self.thumbnail_template.path,
134             self.thumbnail_definition,
135             {}, # TODO proper context
136             lambda name: Font.objects.get(name=name).truetype.path
137         )
138         buf = io.BytesIO()
139         img.save(buf, format='PNG')
140         return buf
141         
142
143 class Card(models.Model):
144     youtube = models.ForeignKey(YouTube, models.CASCADE)
145     order = models.SmallIntegerField()
146     image = models.FileField(upload_to='youtube/card')
147     duration = models.FloatField()
148
149     class Meta:
150         ordering = ('order', )
151
152
153 class Font(models.Model):
154     name = models.CharField(max_length=255, unique=True)
155     truetype = models.FileField(upload_to='youtube/font')
156
157     def __str__(self):
158         return self.name