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

EsupPortail / Esup-Pod / 6377635546

02 Oct 2023 08:21AM UTC coverage: 70.396% (-1.6%) from 71.99%
6377635546

push

github

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

[DONE] #3.4.0

1509 of 1509 new or added lines in 58 files covered. (100.0%)

9288 of 13194 relevant lines covered (70.4%)

0.7 hits per line

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

77.19
/pod/video_encode_transcript/Encoding_video.py
1
"""Encoding video."""
2
import json
1✔
3
import os
1✔
4
import time
1✔
5
from webvtt import WebVTT, Caption
1✔
6
import argparse
1✔
7
import unicodedata
1✔
8

9
if __name__ == "__main__":
1✔
10
    from encoding_utils import (
×
11
        get_info_from_video,
12
        get_list_rendition,
13
        launch_cmd,
14
        check_file,
15
    )
16
    from encoding_settings import (
×
17
        FFMPEG_CMD,
18
        FFPROBE_CMD,
19
        FFPROBE_GET_INFO,
20
        FFMPEG_CRF,
21
        FFMPEG_PRESET,
22
        FFMPEG_PROFILE,
23
        FFMPEG_LEVEL,
24
        FFMPEG_HLS_TIME,
25
        FFMPEG_INPUT,
26
        FFMPEG_LIBX,
27
        FFMPEG_MP4_ENCODE,
28
        FFMPEG_HLS_COMMON_PARAMS,
29
        FFMPEG_HLS_ENCODE_PARAMS,
30
        FFMPEG_MP3_ENCODE,
31
        FFMPEG_M4A_ENCODE,
32
        FFMPEG_NB_THREADS,
33
        FFMPEG_AUDIO_BITRATE,
34
        FFMPEG_EXTRACT_THUMBNAIL,
35
        FFMPEG_NB_THUMBNAIL,
36
        FFMPEG_CREATE_THUMBNAIL,
37
        FFMPEG_EXTRACT_SUBTITLE,
38
    )
39
else:
40
    from .encoding_utils import (
1✔
41
        get_info_from_video,
42
        get_list_rendition,
43
        launch_cmd,
44
        check_file,
45
    )
46
    from .encoding_settings import (
1✔
47
        FFMPEG_CMD,
48
        FFPROBE_CMD,
49
        FFPROBE_GET_INFO,
50
        FFMPEG_CRF,
51
        FFMPEG_PRESET,
52
        FFMPEG_PROFILE,
53
        FFMPEG_LEVEL,
54
        FFMPEG_HLS_TIME,
55
        FFMPEG_INPUT,
56
        FFMPEG_LIBX,
57
        FFMPEG_MP4_ENCODE,
58
        FFMPEG_HLS_COMMON_PARAMS,
59
        FFMPEG_HLS_ENCODE_PARAMS,
60
        FFMPEG_MP3_ENCODE,
61
        FFMPEG_M4A_ENCODE,
62
        FFMPEG_NB_THREADS,
63
        FFMPEG_AUDIO_BITRATE,
64
        FFMPEG_EXTRACT_THUMBNAIL,
65
        FFMPEG_NB_THUMBNAIL,
66
        FFMPEG_CREATE_THUMBNAIL,
67
        FFMPEG_EXTRACT_SUBTITLE,
68
    )
69

70

71
__author__ = "Nicolas CAN <nicolas.can@univ-lille.fr>"
1✔
72
__license__ = "LGPL v3"
1✔
73

74
image_codec = ["jpeg", "gif", "png", "bmp", "jpg"]
1✔
75

76
"""
77
 - Get video source
78
 - get alls track from video source
79
 - encode tracks in HLS and mp4
80
 - save it
81
"""
82

83
try:
1✔
84
    from django.conf import settings
1✔
85

86
    FFMPEG_CMD = getattr(settings, "FFMPEG_CMD", FFMPEG_CMD)
1✔
87
    FFPROBE_CMD = getattr(settings, "FFPROBE_CMD", FFPROBE_CMD)
1✔
88
    FFPROBE_GET_INFO = getattr(settings, "FFPROBE_GET_INFO", FFPROBE_GET_INFO)
1✔
89
    FFMPEG_CRF = getattr(settings, "FFMPEG_CRF", FFMPEG_CRF)
1✔
90
    FFMPEG_PRESET = getattr(settings, "FFMPEG_PRESET", FFMPEG_PRESET)
1✔
91
    FFMPEG_PROFILE = getattr(settings, "FFMPEG_PROFILE", FFMPEG_PROFILE)
1✔
92
    FFMPEG_LEVEL = getattr(settings, "FFMPEG_LEVEL", FFMPEG_LEVEL)
1✔
93
    FFMPEG_HLS_TIME = getattr(settings, "FFMPEG_HLS_TIME", FFMPEG_HLS_TIME)
1✔
94
    FFMPEG_INPUT = getattr(settings, "FFMPEG_INPUT", FFMPEG_INPUT)
1✔
95
    FFMPEG_LIBX = getattr(settings, "FFMPEG_LIBX", FFMPEG_LIBX)
1✔
96
    FFMPEG_MP4_ENCODE = getattr(settings, "FFMPEG_MP4_ENCODE", FFMPEG_MP4_ENCODE)
1✔
97
    FFMPEG_HLS_COMMON_PARAMS = getattr(
1✔
98
        settings, "FFMPEG_HLS_COMMON_PARAMS", FFMPEG_HLS_COMMON_PARAMS
99
    )
100
    FFMPEG_HLS_ENCODE_PARAMS = getattr(
1✔
101
        settings, "FFMPEG_HLS_ENCODE_PARAMS", FFMPEG_HLS_ENCODE_PARAMS
102
    )
103
    FFMPEG_MP3_ENCODE = getattr(settings, "FFMPEG_MP3_ENCODE", FFMPEG_MP3_ENCODE)
1✔
104
    FFMPEG_M4A_ENCODE = getattr(settings, "FFMPEG_M4A_ENCODE", FFMPEG_M4A_ENCODE)
1✔
105
    FFMPEG_NB_THREADS = getattr(settings, "FFMPEG_NB_THREADS", FFMPEG_NB_THREADS)
1✔
106
    FFMPEG_AUDIO_BITRATE = getattr(settings, "FFMPEG_AUDIO_BITRATE", FFMPEG_AUDIO_BITRATE)
1✔
107
    FFMPEG_EXTRACT_THUMBNAIL = getattr(
1✔
108
        settings, "FFMPEG_EXTRACT_THUMBNAIL", FFMPEG_EXTRACT_THUMBNAIL
109
    )
110
    FFMPEG_NB_THUMBNAIL = getattr(settings, "FFMPEG_NB_THUMBNAIL", FFMPEG_NB_THUMBNAIL)
1✔
111
    FFMPEG_CREATE_THUMBNAIL = getattr(
1✔
112
        settings, "FFMPEG_CREATE_THUMBNAIL", FFMPEG_CREATE_THUMBNAIL
113
    )
114
    FFMPEG_EXTRACT_SUBTITLE = getattr(
1✔
115
        settings, "FFMPEG_EXTRACT_SUBTITLE", FFMPEG_EXTRACT_SUBTITLE
116
    )
117
except ImportError:  # pragma: no cover
118
    pass
119

120

121
class Encoding_video:
1✔
122
    """Encoding video object."""
123

124
    id = 0
1✔
125
    video_file = ""
1✔
126
    duration = 0
1✔
127
    list_video_track = {}
1✔
128
    list_audio_track = {}
1✔
129
    list_subtitle_track = {}
1✔
130
    list_image_track = {}
1✔
131
    list_mp4_files = {}
1✔
132
    list_hls_files = {}
1✔
133
    list_mp3_files = {}
1✔
134
    list_m4a_files = {}
1✔
135
    list_thumbnail_files = {}
1✔
136
    list_overview_files = {}
1✔
137
    list_subtitle_files = {}
1✔
138
    encoding_log = {}
1✔
139
    output_dir = ""
1✔
140
    start = 0
1✔
141
    stop = 0
1✔
142
    error_encoding = False
1✔
143
    cutting_start = 0
1✔
144
    cutting_stop = 0
1✔
145

146
    def __init__(self, id=0, video_file="", start=0, stop=0):
1✔
147
        """Initialize a new Encoding_video object."""
148
        self.id = id
1✔
149
        self.video_file = video_file
1✔
150
        self.duration = 0
1✔
151
        self.list_video_track = {}
1✔
152
        self.list_audio_track = {}
1✔
153
        self.list_subtitle_track = {}
1✔
154
        self.list_image_track = {}
1✔
155
        self.list_mp4_files = {}
1✔
156
        self.list_hls_files = {}
1✔
157
        self.list_mp3_files = {}
1✔
158
        self.list_m4a_files = {}
1✔
159
        self.list_thumbnail_files = {}
1✔
160
        self.list_overview_files = {}
1✔
161
        self.encoding_log = {}
1✔
162
        self.list_subtitle_files = {}
1✔
163
        self.output_dir = ""
1✔
164
        self.start = 0
1✔
165
        self.stop = 0
1✔
166
        self.error_encoding = False
1✔
167
        self.cutting_start = start or 0
1✔
168
        self.cutting_stop = stop or 0
1✔
169

170
    def is_video(self):
1✔
171
        """Check if current encoding correspond to a video."""
172
        return len(self.list_video_track) > 0
1✔
173

174
    def get_subtime(self, clip_begin, clip_end):
1✔
175
        subtime = ""
1✔
176
        if clip_begin != 0 or clip_end != 0:
1✔
177
            subtime += "-ss %s " % str(clip_begin) + "-to %s " % str(clip_end)
×
178
        return subtime
1✔
179

180
    def get_video_data(self):
1✔
181
        """Get alls tracks from video source and put it in object passed in parameter."""
182
        msg = "--> get_info_video" + "\n"
1✔
183
        probe_cmd = FFPROBE_GET_INFO % {
1✔
184
            "ffprobe": FFPROBE_CMD,
185
            "select_streams": "",
186
            "source": '"' + self.video_file + '" ',
187
        }
188
        msg += probe_cmd + "\n"
1✔
189
        duration = 0
1✔
190
        info, return_msg = get_info_from_video(probe_cmd)
1✔
191
        msg += json.dumps(info, indent=2)
1✔
192
        msg += " \n"
1✔
193
        msg += return_msg + "\n"
1✔
194
        self.add_encoding_log("probe_cmd", probe_cmd, True, msg)
1✔
195
        try:
1✔
196
            duration = int(float("%s" % info["format"]["duration"]))
1✔
197
        except (RuntimeError, KeyError, AttributeError, ValueError, TypeError) as err:
×
198
            msg = "\nUnexpected error: {0}".format(err)
×
199
            self.add_encoding_log("duration", "", True, msg)
×
200
        if self.cutting_start != 0 or self.cutting_stop != 0:
1✔
201
            duration = self.cutting_stop - self.cutting_start
×
202
        self.duration = duration
1✔
203
        streams = info.get("streams", [])
1✔
204
        for stream in streams:
1✔
205
            self.add_stream(stream)
1✔
206

207
    def fix_duration(self, input_file):
1✔
208
        msg = "--> get_info_video" + "\n"
×
209
        probe_cmd = 'ffprobe -v quiet -show_entries format=duration -hide_banner  \
×
210
                    -of default=noprint_wrappers=1:nokey=1 -print_format json -i \
211
                    "{}"'.format(
212
            input_file
213
        )
214
        info, return_msg = get_info_from_video(probe_cmd)
×
215
        msg += json.dumps(info, indent=2)
×
216
        msg += " \n"
×
217
        msg += return_msg + "\n"
×
218
        duration = 0
×
219
        try:
×
220
            duration = int(float("%s" % info["format"]["duration"]))
×
221
        except (RuntimeError, KeyError, AttributeError, ValueError, TypeError) as err:
×
222
            msg += "\nUnexpected error: {0}".format(err)
×
223
        self.add_encoding_log("fix_duration", "", True, msg)
×
224
        if self.cutting_start != 0 or self.cutting_stop != 0:
×
225
            duration = self.cutting_stop - self.cutting_start
×
226
        self.duration = duration
×
227

228
    def add_stream(self, stream):
1✔
229
        codec_type = stream.get("codec_type", "unknown")
1✔
230
        # https://ffmpeg.org/doxygen/3.2/group__lavu__misc.html#ga9a84bba4713dfced21a1a56163be1f48
231
        if codec_type == "audio":
1✔
232
            codec = stream.get("codec_name", "unknown")
1✔
233
            self.list_audio_track["%s" % stream.get("index")] = {
1✔
234
                "sample_rate": stream.get("sample_rate", 0),
235
                "channels": stream.get("channels", 0),
236
            }
237
        if codec_type == "video":
1✔
238
            codec = stream.get("codec_name", "unknown")
1✔
239
            if any(ext in codec.lower() for ext in image_codec):
1✔
240
                self.list_image_track["%s" % stream.get("index")] = {
×
241
                    "width": stream.get("width", 0),
242
                    "height": stream.get("height", 0),
243
                }
244
            else:
245
                self.list_video_track["%s" % stream.get("index")] = {
1✔
246
                    "width": stream.get("width", 0),
247
                    "height": stream.get("height", 0),
248
                }
249
        if codec_type == "subtitle":
1✔
250
            codec = stream.get("codec_name", "unknown")
×
251
            language = ""
×
252
            if stream.get("tags"):
×
253
                language = stream.get("tags").get("language", "")
×
254
            self.list_subtitle_track["%s" % stream.get("index")] = {"language": language}
×
255

256
    def get_output_dir(self):
1✔
257
        dirname = os.path.dirname(self.video_file)
1✔
258
        return os.path.join(dirname, "%04d" % int(self.id))
1✔
259

260
    def create_output_dir(self):
1✔
261
        output_dir = self.get_output_dir()
1✔
262
        if not os.path.exists(output_dir):
1✔
263
            os.makedirs(output_dir)
1✔
264
        self.output_dir = output_dir
1✔
265

266
    def get_mp4_command(self):
1✔
267
        mp4_command = "%s " % FFMPEG_CMD
1✔
268
        list_rendition = get_list_rendition()
1✔
269
        # remove rendition if encode_mp4 == False
270
        for rend in list_rendition.copy():
1✔
271
            if list_rendition[rend]["encode_mp4"] is False:
1✔
272
                list_rendition.pop(rend)
1✔
273
        if len(list_rendition) == 0:
1✔
274
            return ""
×
275
        first_item = list_rendition.popitem(last=False)
1✔
276
        mp4_command += FFMPEG_INPUT % {
1✔
277
            "input": self.video_file,
278
            "nb_threads": FFMPEG_NB_THREADS,
279
        }
280
        output_file = os.path.join(self.output_dir, "%sp.mp4" % first_item[0])
1✔
281
        mp4_command += FFMPEG_MP4_ENCODE % {
1✔
282
            "cut": self.get_subtime(self.cutting_start, self.cutting_stop),
283
            "map_audio": "-map 0:a:0" if len(self.list_audio_track) > 0 else "",
284
            "libx": FFMPEG_LIBX,
285
            "height": first_item[0],
286
            "preset": FFMPEG_PRESET,
287
            "profile": FFMPEG_PROFILE,
288
            "level": FFMPEG_LEVEL,
289
            "crf": FFMPEG_CRF,
290
            "maxrate": first_item[1]["maxrate"],
291
            "bufsize": first_item[1]["maxrate"],
292
            "ba": first_item[1]["audio_bitrate"],
293
            "output": output_file,
294
        }
295
        self.list_mp4_files[first_item[0]] = output_file
1✔
296
        """
297
        il est possible de faire ainsi :
298
        mp4_command += FFMPEG_MP4_ENCODE.format(
299
            height=first_item[0],
300
            [...]
301
        )
302
        """
303
        in_height = list(self.list_video_track.items())[0][1]["height"]
1✔
304
        for rend in list_rendition:
1✔
305
            resolution_threshold = rend - rend * (
1✔
306
                list_rendition[rend]["encoding_resolution_threshold"] / 100
307
            )
308
            if in_height >= resolution_threshold:
1✔
309
                output_file = os.path.join(self.output_dir, "%sp.mp4" % rend)
×
310
                mp4_command += FFMPEG_MP4_ENCODE % {
×
311
                    "cut": self.get_subtime(self.cutting_start, self.cutting_stop),
312
                    "map_audio": "-map 0:a:0" if len(self.list_audio_track) > 0 else "",
313
                    "libx": FFMPEG_LIBX,
314
                    "height": min(rend, in_height),
315
                    "preset": FFMPEG_PRESET,
316
                    "profile": FFMPEG_PROFILE,
317
                    "level": FFMPEG_LEVEL,
318
                    "crf": FFMPEG_CRF,
319
                    "maxrate": list_rendition[rend]["maxrate"],
320
                    "bufsize": list_rendition[rend]["maxrate"],
321
                    "ba": list_rendition[rend]["audio_bitrate"],
322
                    "output": output_file,
323
                }
324
                self.list_mp4_files[rend] = output_file
×
325
        return mp4_command
1✔
326

327
    def get_hls_command(self):
1✔
328
        hls_command = "%s " % FFMPEG_CMD
1✔
329
        list_rendition = get_list_rendition()
1✔
330
        hls_command += FFMPEG_INPUT % {
1✔
331
            "input": self.video_file,
332
            "nb_threads": FFMPEG_NB_THREADS,
333
        }
334
        hls_common_params = FFMPEG_HLS_COMMON_PARAMS % {
1✔
335
            "cut": self.get_subtime(self.cutting_start, self.cutting_stop),
336
            "libx": FFMPEG_LIBX,
337
            "preset": FFMPEG_PRESET,
338
            "profile": FFMPEG_PROFILE,
339
            "level": FFMPEG_LEVEL,
340
            "crf": FFMPEG_CRF,
341
        }
342
        hls_command += hls_common_params
1✔
343
        in_height = list(self.list_video_track.items())[0][1]["height"]
1✔
344
        for index, rend in enumerate(list_rendition):
1✔
345
            resolution_threshold = rend - rend * (
1✔
346
                list_rendition[rend]["encoding_resolution_threshold"] / 100
347
            )
348
            if in_height >= resolution_threshold or index == 0:
1✔
349
                output_file = os.path.join(self.output_dir, "%sp.m3u8" % rend)
1✔
350
                hls_command += hls_common_params
1✔
351
                hls_command += FFMPEG_HLS_ENCODE_PARAMS % {
1✔
352
                    "height": min(rend, in_height),
353
                    "maxrate": list_rendition[rend]["maxrate"],
354
                    "bufsize": list_rendition[rend]["maxrate"],
355
                    "ba": list_rendition[rend]["audio_bitrate"],
356
                    "hls_time": FFMPEG_HLS_TIME,
357
                    "output": output_file,
358
                }
359
                self.list_hls_files[rend] = output_file
1✔
360
        return hls_command
1✔
361

362
    def encode_video_part(self):
1✔
363
        mp4_command = self.get_mp4_command()
1✔
364
        return_value, return_msg = launch_cmd(mp4_command)
1✔
365
        self.add_encoding_log("mp4_command", mp4_command, return_value, return_msg)
1✔
366
        if not return_value:
1✔
367
            self.error_encoding = True
×
368
        if self.duration == 0:
1✔
369
            list_rendition = get_list_rendition()
×
370
            first_item = list_rendition.popitem(last=False)
×
371
            self.fix_duration(self.list_mp4_files[first_item[0]])
×
372
        hls_command = self.get_hls_command()
1✔
373
        return_value, return_msg = launch_cmd(hls_command)
1✔
374
        if return_value:
1✔
375
            self.create_main_livestream()
1✔
376
        self.add_encoding_log("hls_command", hls_command, return_value, return_msg)
1✔
377

378
    def create_main_livestream(self):
1✔
379
        list_rendition = get_list_rendition()
1✔
380
        livestream_content = ""
1✔
381
        for index, rend in enumerate(list_rendition):
1✔
382
            rend_livestream = os.path.join(
1✔
383
                self.get_output_dir(), "livestream%s.m3u8" % rend
384
            )
385
            if os.path.exists(rend_livestream):
1✔
386
                with open(rend_livestream, "r") as file:
1✔
387
                    data = file.read()
1✔
388
                if index == 0:
1✔
389
                    livestream_content += data
1✔
390
                else:
391
                    livestream_content += "\n".join(data.split("\n")[2:])
×
392
                os.remove(rend_livestream)
1✔
393
        livestream_file = open(
1✔
394
            os.path.join(self.get_output_dir(), "livestream.m3u8"), "w"
395
        )
396
        livestream_file.write(livestream_content.replace("\n\n", "\n"))
1✔
397
        livestream_file.close()
1✔
398

399
    def get_mp3_command(self):
1✔
400
        mp3_command = "%s " % FFMPEG_CMD
1✔
401
        mp3_command += FFMPEG_INPUT % {
1✔
402
            "input": self.video_file,
403
            "nb_threads": FFMPEG_NB_THREADS,
404
        }
405
        output_file = os.path.join(self.output_dir, "audio_%s.mp3" % FFMPEG_AUDIO_BITRATE)
1✔
406
        mp3_command += FFMPEG_MP3_ENCODE % {
1✔
407
            # "audio_bitrate": AUDIO_BITRATE,
408
            "cut": self.get_subtime(self.cutting_start, self.cutting_stop),
409
            "output": output_file,
410
        }
411
        self.list_mp3_files[FFMPEG_AUDIO_BITRATE] = output_file
1✔
412
        return mp3_command
1✔
413

414
    def get_m4a_command(self):
1✔
415
        m4a_command = "%s " % FFMPEG_CMD
1✔
416
        m4a_command += FFMPEG_INPUT % {
1✔
417
            "input": self.video_file,
418
            "nb_threads": FFMPEG_NB_THREADS,
419
        }
420
        output_file = os.path.join(self.output_dir, "audio_%s.m4a" % FFMPEG_AUDIO_BITRATE)
1✔
421
        m4a_command += FFMPEG_M4A_ENCODE % {
1✔
422
            "cut": self.get_subtime(self.cutting_start, self.cutting_stop),
423
            "audio_bitrate": FFMPEG_AUDIO_BITRATE,
424
            "output": output_file,
425
        }
426
        self.list_m4a_files[FFMPEG_AUDIO_BITRATE] = output_file
1✔
427
        return m4a_command
1✔
428

429
    def encode_audio_part(self):
1✔
430
        mp3_command = self.get_mp3_command()
1✔
431
        return_value, return_msg = launch_cmd(mp3_command)
1✔
432
        self.add_encoding_log("mp3_command", mp3_command, return_value, return_msg)
1✔
433
        if self.duration == 0:
1✔
434
            new_k = list(self.list_mp3_files)[0]
×
435
            self.fix_duration(self.list_mp3_files[new_k])
×
436
        if not self.is_video():
1✔
437
            m4a_command = self.get_m4a_command()
1✔
438
            return_value, return_msg = launch_cmd(m4a_command)
1✔
439
            self.add_encoding_log("m4a_command", m4a_command, return_value, return_msg)
1✔
440

441
    def get_extract_thumbnail_command(self):
1✔
442
        thumbnail_command = "%s " % FFMPEG_CMD
×
443
        thumbnail_command += FFMPEG_INPUT % {
×
444
            "input": self.video_file,
445
            "nb_threads": FFMPEG_NB_THREADS,
446
        }
447
        for img in self.list_image_track:
×
448
            output_file = os.path.join(self.output_dir, "thumbnail_%s.png" % img)
×
449
            thumbnail_command += FFMPEG_EXTRACT_THUMBNAIL % {
×
450
                "index": img,
451
                "output": output_file,
452
            }
453
            self.list_thumbnail_files[img] = output_file
×
454
        return thumbnail_command
×
455

456
    def get_create_thumbnail_command(self):
1✔
457
        thumbnail_command = "%s " % FFMPEG_CMD
1✔
458
        first_item = self.get_first_item()
1✔
459
        input_file = self.list_mp4_files[first_item[0]]
1✔
460
        thumbnail_command += FFMPEG_INPUT % {
1✔
461
            "input": input_file,
462
            "nb_threads": FFMPEG_NB_THREADS,
463
        }
464
        output_file = os.path.join(self.output_dir, "thumbnail")
1✔
465
        thumbnail_command += FFMPEG_CREATE_THUMBNAIL % {
1✔
466
            "duration": self.duration,
467
            "nb_thumbnail": FFMPEG_NB_THUMBNAIL,
468
            "output": output_file,
469
        }
470
        for nb in range(0, FFMPEG_NB_THUMBNAIL):
1✔
471
            num_thumb = str(nb + 1)
1✔
472
            self.list_thumbnail_files[num_thumb] = "%s_000%s.png" % (
1✔
473
                output_file,
474
                num_thumb,
475
            )
476
        return thumbnail_command
1✔
477

478
    def get_first_item(self):
1✔
479
        """get the first mp4 render from setting"""
480
        list_rendition = get_list_rendition()
1✔
481
        for rend in list_rendition.copy():
1✔
482
            if list_rendition[rend]["encode_mp4"] is False:
1✔
483
                list_rendition.pop(rend)
1✔
484
        if len(list_rendition) == 0:
1✔
485
            return None
×
486
        else:
487
            return list_rendition.popitem(last=False)
1✔
488

489
    def create_overview(self):
1✔
490
        first_item = self.get_first_item()
1✔
491
        # overview combine for 160x90
492
        in_height = list(self.list_video_track.items())[0][1]["height"]
1✔
493
        in_width = list(self.list_video_track.items())[0][1]["width"]
1✔
494
        image_height = 75  # decrease to 75 px instead of 90 due to montage overflow
1✔
495
        coef = in_height / image_height
1✔
496
        image_width = int(in_width / coef)
1✔
497
        input_file = self.list_mp4_files[first_item[0]]
1✔
498
        nb_img = 100
1✔
499
        step = 1
1✔
500
        if self.duration < 100:
1✔
501
            # nb_img = int(self.duration * 10 / 100)
502
            step = 10  # on ne fait que 10 images si la video dure moins de 100 sec.
1✔
503
        overviewimagefilename = os.path.join(self.output_dir, "overview.png")
1✔
504
        image_url = os.path.basename(overviewimagefilename)
1✔
505
        overviewfilename = os.path.join(self.output_dir, "overview.vtt")
1✔
506
        webvtt = WebVTT()
1✔
507
        for i in range(0, nb_img, step):
1✔
508
            # create overviewimagefilename for first pass
509
            output_file = (
1✔
510
                os.path.join(self.output_dir, "thumbnail_%s.png" % i)
511
                if i > 0
512
                else overviewimagefilename
513
            )
514
            cmd_ffmpegthumbnailer = (
1✔
515
                'ffmpegthumbnailer -t "%(stamp)s" '
516
                + '-s "%(image_width)s" -i %(source)s -c png '
517
                + "-o %(output_file)s "
518
            ) % {
519
                "stamp": str(i) + "%",
520
                "source": input_file,
521
                "output_file": output_file,
522
                "image_width": image_width,
523
            }
524
            return_value, return_msg = launch_cmd(cmd_ffmpegthumbnailer)
1✔
525
            # self.add_encoding_log(
526
            # "ffmpegthumbnailer_%s" % i, cmd_ffmpegthumbnailer, return_value, return_msg)
527
            if return_value and check_file(output_file) and i > 0:
1✔
528
                # print("MONTAGE")
529
                cmd_montage = (
1✔
530
                    "montage -geometry +0+0 %(overviewimagefilename)s \
531
                    %(output_file)s  %(overviewimagefilename)s"
532
                    % {
533
                        "overviewimagefilename": overviewimagefilename,
534
                        "output_file": output_file,
535
                    }
536
                )
537
                return_value, return_msg = launch_cmd(cmd_montage)
1✔
538
                if not return_value:
1✔
539
                    print("cmd_montage_%s" % i, cmd_montage, return_value, return_msg)
×
540
                # self.add_encoding_log
541
                # ("cmd_montage_%s" % i, cmd_montage, return_value, return_msg)
542
                os.remove(output_file)
1✔
543
            start = format(float(self.duration * i / 100), ".3f")
1✔
544
            end = format(float(self.duration * (i + step) / 100), ".3f")
1✔
545
            start_time = time.strftime(
1✔
546
                "%H:%M:%S", time.gmtime(int(str(start).split(".")[0]))
547
            )
548
            start_time += ".%s" % (str(start).split(".")[1])
1✔
549
            end_time = time.strftime(
1✔
550
                "%H:%M:%S", time.gmtime(int(str(end).split(".")[0]))
551
            ) + ".%s" % (str(end).split(".")[1])
552
            caption = Caption(
1✔
553
                "%s" % start_time,
554
                "%s" % end_time,
555
                "%s#xywh=%d,%d,%d,%d"
556
                % (image_url, image_width * (i / step), 0, image_width, image_height),
557
            )
558
            webvtt.captions.append(caption)
1✔
559
        webvtt.save(overviewfilename)
1✔
560
        if check_file(overviewfilename) and check_file(overviewimagefilename):
1✔
561
            self.list_overview_files["0"] = overviewimagefilename
1✔
562
            self.list_overview_files["1"] = overviewfilename
1✔
563
            # self.encoding_log += "\n- overviewfilename:\n%s" % overviewfilename
564
        else:
565
            self.add_encoding_log("create_overview", "", False, "")
×
566

567
    def encode_image_part(self):
1✔
568
        if len(self.list_image_track) > 0:
1✔
569
            thumbnail_command = self.get_extract_thumbnail_command()
×
570
            return_value, return_msg = launch_cmd(thumbnail_command)
×
571
            self.add_encoding_log(
×
572
                "extract_thumbnail_command", thumbnail_command, return_value, return_msg
573
            )
574
        elif self.is_video():
1✔
575
            thumbnail_command = self.get_create_thumbnail_command()
1✔
576
            return_value, return_msg = launch_cmd(thumbnail_command)
1✔
577
            self.add_encoding_log(
1✔
578
                "create_thumbnail_command", thumbnail_command, return_value, return_msg
579
            )
580
        # on ne fait pas d'overview pour les videos de moins de 10 secondes
581
        # (laisser les 10sec inclus pour laisser les tests passer) --> OK
582
        if self.is_video() and self.duration >= 10:
1✔
583
            self.create_overview()
1✔
584

585
    def get_extract_subtitle_command(self):
1✔
586
        subtitle_command = "%s " % FFMPEG_CMD
×
587
        subtitle_command += FFMPEG_INPUT % {
×
588
            "input": self.video_file,
589
            "nb_threads": FFMPEG_NB_THREADS,
590
        }
591
        for sub in self.list_subtitle_track:
×
592
            lang = self.list_subtitle_track[sub]["language"]
×
593
            output_file = os.path.join(self.output_dir, "subtitle_%s.vtt" % lang)
×
594
            subtitle_command += FFMPEG_EXTRACT_SUBTITLE % {
×
595
                "index": sub,
596
                "output": output_file,
597
            }
598
            self.list_subtitle_files[sub] = [lang, output_file]
×
599
        return subtitle_command
×
600

601
    def get_subtitle_part(self):
1✔
602
        if len(self.list_subtitle_track) > 0:
×
603
            subtitle_command = self.get_extract_subtitle_command()
×
604
            return_value, return_msg = launch_cmd(subtitle_command)
×
605
            self.add_encoding_log(
×
606
                "subtitle_command", subtitle_command, return_value, return_msg
607
            )
608

609
    def export_to_json(self):
1✔
610
        data_to_dump = {}
1✔
611
        for attribute, value in self.__dict__.items():
1✔
612
            data_to_dump[attribute] = value
1✔
613
        with open(self.output_dir + "/info_video.json", "w") as outfile:
1✔
614
            json.dump(data_to_dump, outfile, indent=2)
1✔
615

616
    def add_encoding_log(self, title, command, result, msg):
1✔
617
        self.encoding_log[title] = {"command": command, "result": result, "msg": msg}
1✔
618
        if result is False and self.error_encoding is False:
1✔
619
            self.error_encoding = True
1✔
620

621
    def start_encode(self):
1✔
622
        self.start = time.ctime()
1✔
623
        self.create_output_dir()
1✔
624
        self.get_video_data()
1✔
625
        print(self.id, self.video_file, self.duration)
1✔
626
        if self.is_video():
1✔
627
            self.encode_video_part()
1✔
628
        if len(self.list_audio_track) > 0:
1✔
629
            self.encode_audio_part()
1✔
630
        self.encode_image_part()
1✔
631
        if len(self.list_subtitle_track) > 0:
1✔
632
            self.get_subtitle_part()
×
633
        self.stop = time.ctime()
1✔
634
        self.export_to_json()
1✔
635

636

637
def fix_input(input):
1✔
638
    filename = ""
×
639
    if args.input.startswith("/"):
×
640
        path_file = args.input
×
641
    else:
642
        path_file = os.path.join(os.getcwd(), args.input)
×
643
    if os.access(path_file, os.F_OK) and os.stat(path_file).st_size > 0:
×
644
        # remove accent and space
645
        filename = "".join(
×
646
            (
647
                c
648
                for c in unicodedata.normalize("NFD", path_file)
649
                if unicodedata.category(c) != "Mn"
650
            )
651
        )
652
        filename = filename.replace(" ", "_")
×
653
        os.rename(
×
654
            path_file,
655
            filename,
656
        )
657
        print("Encoding file {} \n".format(filename))
×
658
    return filename
×
659

660

661
"""
662
  remote encode ???
663
"""
664
if __name__ == "__main__":
1✔
665
    start = "Start at: %s" % time.ctime()
×
666
    parser = argparse.ArgumentParser(description="Running encoding video.")
×
667
    parser.add_argument("--id", required=True, help="the ID of the video")
×
668
    parser.add_argument("--start", required=False, help="Start cut")
×
669
    parser.add_argument("--stop", required=False, help="Stop cut")
×
670
    parser.add_argument("--input", required=True, help="name of input file to encode")
×
671

672
    args = parser.parse_args()
×
673
    print(args.start)
×
674
    filename = fix_input(args.input)
×
675
    encoding_video = Encoding_video(args.id, filename, args.start, args.stop)
×
676
    # error if uncommented
677
    # encoding_video.encoding_log += start
678
    # AttributeError: 'NoneType' object has no attribute 'get'
679
    encoding_video.start_encode()
×
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