• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

EsupPortail / Esup-Pod / 5322792349

pending completion
5322792349

push

github

web-flow
Merge pull request #863 from EsupPortail/develop

#3.3.0

- Import external video from url, youtube, peertube and BigBlueButton
- Change xapi actor to deal with Moodle
- Update template to BS5.3 and improve compliance for W3C
- Use redis to cache session and improve logging
- refactor of encoding/transcripting to move it in separate application
- Fixbug on categories, recorder, user liste, tags cloud

1484 of 1484 new or added lines in 51 files covered. (100.0%)

9011 of 12517 relevant lines covered (71.99%)

0.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.19
/pod/video_encode_transcript/models.py
1
"""Models for Esup-Pod video_encode."""
2
import os
1✔
3

4
from django.db import models
1✔
5
from django.conf import settings
1✔
6
from django.utils.translation import ugettext_lazy as _
1✔
7
from django.core.exceptions import ValidationError
1✔
8
from django.core.validators import MaxValueValidator
1✔
9
from django.contrib.sites.models import Site
1✔
10
from django.dispatch import receiver
1✔
11
from django.db.models.signals import post_save
1✔
12
from pod.video.models import Video
1✔
13
from pod.video.utils import get_storage_path_video
1✔
14

15
ENCODING_CHOICES = getattr(
1✔
16
    settings,
17
    "ENCODING_CHOICES",
18
    (
19
        ("audio", "audio"),
20
        ("360p", "360p"),
21
        ("480p", "480p"),
22
        ("720p", "720p"),
23
        ("1080p", "1080p"),
24
        ("playlist", "playlist"),
25
    ),
26
)
27

28
FORMAT_CHOICES = getattr(
1✔
29
    settings,
30
    "FORMAT_CHOICES",
31
    (
32
        ("video/mp4", "video/mp4"),
33
        ("video/mp2t", "video/mp2t"),
34
        ("video/webm", "video/webm"),
35
        ("audio/mp3", "audio/mp3"),
36
        ("audio/wav", "audio/wav"),
37
        ("application/x-mpegURL", "application/x-mpegURL"),
38
    ),
39
)
40

41

42
class VideoRendition(models.Model):
1✔
43
    """Model representing the rendition video."""
44

45
    resolution = models.CharField(
1✔
46
        _("resolution"),
47
        max_length=50,
48
        unique=True,
49
        help_text=_(
50
            "Please use the only format x. i.e.: "
51
            + "<em>640x360</em> or <em>1280x720</em>"
52
            + " or <em>1920x1080</em>."
53
        ),
54
    )
55
    minrate = models.CharField(
1✔
56
        _("minrate"),
57
        max_length=50,
58
        help_text=_(
59
            "Please use the only format k. i.e.: "
60
            + "<em>300k</em> or <em>600k</em>"
61
            + " or <em>1000k</em>."
62
        ),
63
    )
64
    video_bitrate = models.CharField(
1✔
65
        _("bitrate video"),
66
        max_length=50,
67
        help_text=_(
68
            "Please use the only format k. i.e.: "
69
            + "<em>300k</em> or <em>600k</em>"
70
            + " or <em>1000k</em>."
71
        ),
72
    )
73
    maxrate = models.CharField(
1✔
74
        _("maxrate"),
75
        max_length=50,
76
        help_text=_(
77
            "Please use the only format k. i.e.: "
78
            + "<em>300k</em> or <em>600k</em>"
79
            + " or <em>1000k</em>."
80
        ),
81
    )
82
    encoding_resolution_threshold = models.PositiveIntegerField(
1✔
83
        _("encoding resolution threshold"), default=0, validators=[MaxValueValidator(100)]
84
    )
85
    audio_bitrate = models.CharField(
1✔
86
        _("bitrate audio"),
87
        max_length=50,
88
        help_text=_(
89
            "Please use the only format k. i.e.: "
90
            + "<em>300k</em> or <em>600k</em>"
91
            + " or <em>1000k</em>."
92
        ),
93
    )
94
    encode_mp4 = models.BooleanField(_("Make a MP4 version"), default=False)
1✔
95
    sites = models.ManyToManyField(Site)
1✔
96

97
    @property
1✔
98
    def height(self):
1✔
99
        """The height of the video rendition based on the resolution."""
100
        return int(self.resolution.split("x")[1])
×
101

102
    @property
1✔
103
    def width(self):
1✔
104
        """The width of the video rendition based on the resolution."""
105
        return int(self.resolution.split("x")[0])
×
106

107
    class Meta:
1✔
108
        # ordering = ['height'] # Not work
109
        verbose_name = _("rendition")
1✔
110
        verbose_name_plural = _("renditions")
1✔
111

112
    def __str__(self):
1✔
113
        return "VideoRendition num %s with resolution %s" % (
1✔
114
            "%04d" % self.id,
115
            self.resolution,
116
        )
117

118
    def bitrate(self, field_value, field_name, name=None):
1✔
119
        """Validate the bitrate field value."""
120
        if name is None:
1✔
121
            name = field_name
1✔
122
        if field_value and "k" not in field_value:
1✔
123
            msg = "Error in %s: " % _(name)
1✔
124
            raise ValidationError(
1✔
125
                "%s %s" % (msg, VideoRendition._meta.get_field(field_name).help_text)
126
            )
127
        else:
128
            vb = field_value.replace("k", "")
1✔
129
            if not vb.isdigit():
1✔
130
                msg = "Error in %s: " % _(name)
1✔
131
                raise ValidationError(
1✔
132
                    "%s %s" % (msg, VideoRendition._meta.get_field(field_name).help_text)
133
                )
134

135
    def clean_bitrate(self):
1✔
136
        """Clean the bitrate-related fields."""
137
        self.bitrate(self.video_bitrate, "video_bitrate", "bitrate video")
1✔
138
        self.bitrate(self.maxrate, "maxrate")
1✔
139
        self.bitrate(self.minrate, "minrate")
1✔
140

141
    def clean(self):
1✔
142
        """Clean the fields of the VideoRendition model."""
143
        if self.resolution and "x" not in self.resolution:
1✔
144
            raise ValidationError(VideoRendition._meta.get_field("resolution").help_text)
1✔
145
        else:
146
            res = self.resolution.replace("x", "")
1✔
147
            if not res.isdigit():
1✔
148
                raise ValidationError(
1✔
149
                    VideoRendition._meta.get_field("resolution").help_text
150
                )
151

152
        self.clean_bitrate()
1✔
153
        self.bitrate(self.audio_bitrate, "audio_bitrate", "bitrate audio")
1✔
154

155

156
@receiver(post_save, sender=VideoRendition)
1✔
157
def default_site_videorendition(sender, instance, created, **kwargs):
1✔
158
    """Add the current site as a default site."""
159
    if len(instance.sites.all()) == 0:
1✔
160
        instance.sites.add(Site.objects.get_current())
1✔
161

162

163
class EncodingVideo(models.Model):
1✔
164
    """Model representing the encoding video for a video."""
165

166
    name = models.CharField(
1✔
167
        _("Name"),
168
        max_length=10,
169
        choices=ENCODING_CHOICES,
170
        default="360p",
171
        help_text=_("Please use the only format in encoding choices:")
172
        + " %s" % " ".join(str(key) for key, value in ENCODING_CHOICES),
173
    )
174
    video = models.ForeignKey(Video, verbose_name=_("Video"), on_delete=models.CASCADE)
1✔
175
    rendition = models.ForeignKey(
1✔
176
        VideoRendition, verbose_name=_("rendition"), on_delete=models.CASCADE
177
    )
178
    encoding_format = models.CharField(
1✔
179
        _("Format"),
180
        max_length=22,
181
        choices=FORMAT_CHOICES,
182
        default="video/mp4",
183
        help_text=_("Please use the only format in format choices:")
184
        + " %s" % " ".join(str(key) for key, value in FORMAT_CHOICES),
185
    )
186
    source_file = models.FileField(
1✔
187
        _("encoding source file"),
188
        upload_to=get_storage_path_video,
189
        max_length=255,
190
    )
191

192
    @property
1✔
193
    def sites(self):
1✔
194
        """Property representing the sites associated with the video."""
195
        return self.video.sites
×
196

197
    @property
1✔
198
    def sites_all(self):
1✔
199
        """Property representing all the sites associated with the video."""
200
        return self.video.sites_set.all()
×
201

202
    def clean(self):
1✔
203
        """Validate the encoding video model."""
204
        if self.name:
1✔
205
            if self.name not in dict(ENCODING_CHOICES):
1✔
206
                raise ValidationError(EncodingVideo._meta.get_field("name").help_text)
1✔
207
        if self.encoding_format:
1✔
208
            if self.encoding_format not in dict(FORMAT_CHOICES):
1✔
209
                raise ValidationError(
×
210
                    EncodingVideo._meta.get_field("encoding_format").help_text
211
                )
212

213
    class Meta:
1✔
214
        ordering = ["name"]
1✔
215
        verbose_name = _("Encoding video")
1✔
216
        verbose_name_plural = _("Encoding videos")
1✔
217

218
    def __str__(self):
1✔
219
        return "EncodingVideo num: %s with resolution %s for video %s in %s" % (
1✔
220
            "%04d" % self.id,
221
            self.name,
222
            self.video.id,
223
            self.encoding_format,
224
        )
225

226
    @property
1✔
227
    def owner(self):
1✔
228
        """Property representing the owner of the video."""
229
        return self.video.owner
1✔
230

231
    @property
1✔
232
    def height(self):
1✔
233
        """Property representing the height of the video rendition."""
234
        return int(self.rendition.resolution.split("x")[1])
×
235

236
    @property
1✔
237
    def width(self):
1✔
238
        """Property representing the width of the video rendition."""
239
        return int(self.rendition.resolution.split("x")[0])
×
240

241
    def delete(self):
1✔
242
        """Delete the encoding video."""
243
        if self.source_file:
1✔
244
            if os.path.isfile(self.source_file.path):
1✔
245
                os.remove(self.source_file.path)
1✔
246
        super(EncodingVideo, self).delete()
1✔
247

248

249
class EncodingAudio(models.Model):
1✔
250
    """Model representing the encoding audio for a video."""
251

252
    name = models.CharField(
1✔
253
        _("Name"),
254
        max_length=10,
255
        choices=ENCODING_CHOICES,
256
        default="audio",
257
        help_text=_("Please use the only format in encoding choices:")
258
        + " %s" % " ".join(str(key) for key, value in ENCODING_CHOICES),
259
    )
260
    video = models.ForeignKey(Video, verbose_name=_("Video"), on_delete=models.CASCADE)
1✔
261
    encoding_format = models.CharField(
1✔
262
        _("Format"),
263
        max_length=22,
264
        choices=FORMAT_CHOICES,
265
        default="audio/mp3",
266
        help_text=_("Please use the only format in format choices:")
267
        + " %s" % " ".join(str(key) for key, value in FORMAT_CHOICES),
268
    )
269
    source_file = models.FileField(
1✔
270
        _("encoding source file"),
271
        upload_to=get_storage_path_video,
272
        max_length=255,
273
    )
274

275
    @property
1✔
276
    def sites(self):
1✔
277
        """Property representing the sites associated with the video."""
278
        return self.video.sites
×
279

280
    @property
1✔
281
    def sites_all(self):
1✔
282
        """Property representing all the sites associated with the video."""
283
        return self.video.sites_set.all()
×
284

285
    class Meta:
1✔
286
        ordering = ["name"]
1✔
287
        verbose_name = _("Encoding audio")
1✔
288
        verbose_name_plural = _("Encoding audios")
1✔
289

290
    def clean(self):
1✔
291
        """Validate the encoding audio model."""
292
        if self.name:
1✔
293
            if self.name not in dict(ENCODING_CHOICES):
1✔
294
                raise ValidationError(EncodingAudio._meta.get_field("name").help_text)
1✔
295
        if self.encoding_format:
1✔
296
            if self.encoding_format not in dict(FORMAT_CHOICES):
1✔
297
                raise ValidationError(
×
298
                    EncodingAudio._meta.get_field("encoding_format").help_text
299
                )
300

301
    def __str__(self):
1✔
302
        return "EncodingAudio num: %s for video %s in %s" % (
1✔
303
            "%04d" % self.id,
304
            self.video.id,
305
            self.encoding_format,
306
        )
307

308
    @property
1✔
309
    def owner(self):
1✔
310
        """Property representing the owner of the video."""
311
        return self.video.owner
1✔
312

313
    def delete(self):
1✔
314
        """Delete the encoding audio, including the source file if it exists."""
315
        if self.source_file:
1✔
316
            if os.path.isfile(self.source_file.path):
1✔
317
                os.remove(self.source_file.path)
1✔
318
        super(EncodingAudio, self).delete()
1✔
319

320

321
class EncodingLog(models.Model):
1✔
322
    """Model representing the encoding log for a video."""
323

324
    video = models.OneToOneField(
1✔
325
        Video, verbose_name=_("Video"), editable=False, on_delete=models.CASCADE
326
    )
327
    log = models.TextField(null=True, blank=True, editable=False)
1✔
328
    logfile = models.FileField(max_length=255, blank=True, null=True)
1✔
329

330
    @property
1✔
331
    def sites(self):
1✔
332
        """Property representing the sites associated with the video."""
333
        return self.video.sites
×
334

335
    @property
1✔
336
    def sites_all(self):
1✔
337
        """Property representing all the sites associated with the video."""
338
        return self.video.sites_set.all()
×
339

340
    class Meta:
1✔
341
        ordering = ["video"]
1✔
342
        verbose_name = _("Encoding log")
1✔
343
        verbose_name_plural = _("Encoding logs")
1✔
344

345
    def __str__(self):
1✔
346
        return "Log for encoding video %s" % (self.video.id)
1✔
347

348

349
class EncodingStep(models.Model):
1✔
350
    """Model representing an encoding step for a video."""
351

352
    video = models.OneToOneField(
1✔
353
        Video, verbose_name=_("Video"), editable=False, on_delete=models.CASCADE
354
    )
355
    num_step = models.IntegerField(default=0, editable=False)
1✔
356
    desc_step = models.CharField(null=True, max_length=255, blank=True, editable=False)
1✔
357

358
    @property
1✔
359
    def sites(self):
1✔
360
        """Property representing the sites associated with the video."""
361
        return self.video.sites
×
362

363
    @property
1✔
364
    def sites_all(self):
1✔
365
        """Property representing all the sites associated with the video."""
366
        return self.video.sites_set.all()
×
367

368
    class Meta:
1✔
369
        ordering = ["video"]
1✔
370
        verbose_name = _("Encoding step")
1✔
371
        verbose_name_plural = _("Encoding steps")
1✔
372

373
    def __str__(self):
1✔
374
        return "Step for encoding video %s" % (self.video.id)
1✔
375

376

377
class PlaylistVideo(models.Model):
1✔
378
    name = models.CharField(
1✔
379
        _("Name"),
380
        max_length=10,
381
        choices=ENCODING_CHOICES,
382
        default="360p",
383
        help_text=_("Please use the only format in encoding choices:")
384
        + " %s" % " ".join(str(key) for key, value in ENCODING_CHOICES),
385
    )
386
    video = models.ForeignKey(Video, verbose_name=_("Video"), on_delete=models.CASCADE)
1✔
387
    encoding_format = models.CharField(
1✔
388
        _("Format"),
389
        max_length=22,
390
        choices=FORMAT_CHOICES,
391
        default="application/x-mpegURL",
392
        help_text=_("Please use the only format in format choices:")
393
        + " %s" % " ".join(str(key) for key, value in FORMAT_CHOICES),
394
    )
395
    source_file = models.FileField(
1✔
396
        _("encoding source file"),
397
        upload_to=get_storage_path_video,
398
        max_length=255,
399
    )
400

401
    class Meta:
1✔
402
        verbose_name = _("Video Playlist")
1✔
403
        verbose_name_plural = _("Video Playlists")
1✔
404

405
    @property
1✔
406
    def sites(self):
1✔
407
        return self.video.sites
×
408

409
    @property
1✔
410
    def sites_all(self):
1✔
411
        return self.video.sites_set.all()
×
412

413
    def clean(self):
1✔
414
        """Validate some PlaylistVideomodels fields."""
415
        if self.name:
1✔
416
            if self.name not in dict(ENCODING_CHOICES):
1✔
417
                raise ValidationError(
1✔
418
                    PlaylistVideo._meta.get_field("name").help_text, code="invalid_name"
419
                )
420
        if self.encoding_format:
1✔
421
            if self.encoding_format not in dict(FORMAT_CHOICES):
1✔
422
                raise ValidationError(
×
423
                    PlaylistVideo._meta.get_field("encoding_format").help_text,
424
                    code="invalid_encoding",
425
                )
426

427
    def __str__(self):
1✔
428
        return "Playlist num: %s for video %s in %s" % (
1✔
429
            "%04d" % self.id,
430
            self.video.id,
431
            self.encoding_format,
432
        )
433

434
    @property
1✔
435
    def owner(self):
1✔
436
        return self.video.owner
1✔
437

438
    def delete(self):
1✔
439
        if self.source_file:
1✔
440
            if os.path.isfile(self.source_file.path):
1✔
441
                os.remove(self.source_file.path)
1✔
442
        super(PlaylistVideo, self).delete()
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc