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

EsupPortail / Esup-Pod / 7179389481

12 Dec 2023 09:24AM UTC coverage: 70.116%. First build
7179389481

Pull #1006

github

web-flow
Merge e2a9e2b59 into 1d788ce13
Pull Request #1006: [DONE] Feature dressing video

114 of 220 new or added lines in 9 files covered. (51.82%)

9493 of 13539 relevant lines covered (70.12%)

0.7 hits per line

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

70.11
/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
from pod.dressing.utils import get_position_value, get_dressing_input
1✔
9

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

83

84
__author__ = "Nicolas CAN <nicolas.can@univ-lille.fr>"
1✔
85
__license__ = "LGPL v3"
1✔
86

87
image_codec = ["jpeg", "gif", "png", "bmp", "jpg"]
1✔
88

89
"""
90
 - Get video source
91
 - get alls track from video source
92
 - encode tracks in HLS and mp4
93
 - save it
94
"""
95

96
try:
1✔
97
    from django.conf import settings
1✔
98

99
    FFMPEG_CMD = getattr(settings, "FFMPEG_CMD", FFMPEG_CMD)
1✔
100
    FFPROBE_CMD = getattr(settings, "FFPROBE_CMD", FFPROBE_CMD)
1✔
101
    FFPROBE_GET_INFO = getattr(settings, "FFPROBE_GET_INFO", FFPROBE_GET_INFO)
1✔
102
    FFMPEG_CRF = getattr(settings, "FFMPEG_CRF", FFMPEG_CRF)
1✔
103
    FFMPEG_PRESET = getattr(settings, "FFMPEG_PRESET", FFMPEG_PRESET)
1✔
104
    FFMPEG_PROFILE = getattr(settings, "FFMPEG_PROFILE", FFMPEG_PROFILE)
1✔
105
    FFMPEG_LEVEL = getattr(settings, "FFMPEG_LEVEL", FFMPEG_LEVEL)
1✔
106
    FFMPEG_HLS_TIME = getattr(settings, "FFMPEG_HLS_TIME", FFMPEG_HLS_TIME)
1✔
107
    FFMPEG_INPUT = getattr(settings, "FFMPEG_INPUT", FFMPEG_INPUT)
1✔
108
    FFMPEG_LIBX = getattr(settings, "FFMPEG_LIBX", FFMPEG_LIBX)
1✔
109
    FFMPEG_MP4_ENCODE = getattr(settings, "FFMPEG_MP4_ENCODE", FFMPEG_MP4_ENCODE)
1✔
110
    FFMPEG_HLS_COMMON_PARAMS = getattr(
1✔
111
        settings, "FFMPEG_HLS_COMMON_PARAMS", FFMPEG_HLS_COMMON_PARAMS
112
    )
113
    FFMPEG_HLS_ENCODE_PARAMS = getattr(
1✔
114
        settings, "FFMPEG_HLS_ENCODE_PARAMS", FFMPEG_HLS_ENCODE_PARAMS
115
    )
116
    FFMPEG_MP3_ENCODE = getattr(settings, "FFMPEG_MP3_ENCODE", FFMPEG_MP3_ENCODE)
1✔
117
    FFMPEG_M4A_ENCODE = getattr(settings, "FFMPEG_M4A_ENCODE", FFMPEG_M4A_ENCODE)
1✔
118
    FFMPEG_NB_THREADS = getattr(settings, "FFMPEG_NB_THREADS", FFMPEG_NB_THREADS)
1✔
119
    FFMPEG_AUDIO_BITRATE = getattr(settings, "FFMPEG_AUDIO_BITRATE", FFMPEG_AUDIO_BITRATE)
1✔
120
    FFMPEG_EXTRACT_THUMBNAIL = getattr(
1✔
121
        settings, "FFMPEG_EXTRACT_THUMBNAIL", FFMPEG_EXTRACT_THUMBNAIL
122
    )
123
    FFMPEG_NB_THUMBNAIL = getattr(settings, "FFMPEG_NB_THUMBNAIL", FFMPEG_NB_THUMBNAIL)
1✔
124
    FFMPEG_CREATE_THUMBNAIL = getattr(
1✔
125
        settings, "FFMPEG_CREATE_THUMBNAIL", FFMPEG_CREATE_THUMBNAIL
126
    )
127
    FFMPEG_EXTRACT_SUBTITLE = getattr(
1✔
128
        settings, "FFMPEG_EXTRACT_SUBTITLE", FFMPEG_EXTRACT_SUBTITLE
129
    )
130
    FFMPEG_DRESSING_INPUT = getattr(
1✔
131
        settings, "FFMPEG_DRESSING_INPUT", FFMPEG_DRESSING_INPUT
132
    )
133
    FFMPEG_DRESSING_OUTPUT = getattr(
1✔
134
        settings, "FFMPEG_DRESSING_OUTPUT", FFMPEG_DRESSING_OUTPUT
135
    )
136
    FFMPEG_DRESSING_WATERMARK = getattr(
1✔
137
        settings, "FFMPEG_DRESSING_WATERMARK", FFMPEG_DRESSING_WATERMARK
138
    )
139
    FFMPEG_DRESSING_FILTER_COMPLEX = getattr(
1✔
140
        settings, "FFMPEG_DRESSING_FILTER_COMPLEX", FFMPEG_DRESSING_FILTER_COMPLEX
141
    )
142
    FFMPEG_DRESSING_SCALE = getattr(
1✔
143
        settings, "FFMPEG_DRESSING_SCALE", FFMPEG_DRESSING_SCALE
144
    )
145
    FFMPEG_DRESSING_CONCAT = getattr(
1✔
146
        settings, "FFMPEG_DRESSING_CONCAT", FFMPEG_DRESSING_CONCAT
147
    )
148
except ImportError:  # pragma: no cover
149
    pass
150

151

152
class Encoding_video:
1✔
153
    """Encoding video object."""
154

155
    id = 0
1✔
156
    video_file = ""
1✔
157
    duration = 0
1✔
158
    list_video_track = {}
1✔
159
    list_audio_track = {}
1✔
160
    list_subtitle_track = {}
1✔
161
    list_image_track = {}
1✔
162
    list_mp4_files = {}
1✔
163
    list_hls_files = {}
1✔
164
    list_mp3_files = {}
1✔
165
    list_m4a_files = {}
1✔
166
    list_thumbnail_files = {}
1✔
167
    list_overview_files = {}
1✔
168
    list_subtitle_files = {}
1✔
169
    encoding_log = {}
1✔
170
    output_dir = ""
1✔
171
    start = 0
1✔
172
    stop = 0
1✔
173
    error_encoding = False
1✔
174
    cutting_start = 0
1✔
175
    cutting_stop = 0
1✔
176
    dressing = None
1✔
177

178
    def __init__(self, id=0, video_file="", start=0, stop=0, dressing=None):
1✔
179
        """Initialize a new Encoding_video object."""
180
        self.id = id
1✔
181
        self.video_file = video_file
1✔
182
        self.duration = 0
1✔
183
        self.list_video_track = {}
1✔
184
        self.list_audio_track = {}
1✔
185
        self.list_subtitle_track = {}
1✔
186
        self.list_image_track = {}
1✔
187
        self.list_mp4_files = {}
1✔
188
        self.list_hls_files = {}
1✔
189
        self.list_mp3_files = {}
1✔
190
        self.list_m4a_files = {}
1✔
191
        self.list_thumbnail_files = {}
1✔
192
        self.list_overview_files = {}
1✔
193
        self.encoding_log = {}
1✔
194
        self.list_subtitle_files = {}
1✔
195
        self.output_dir = ""
1✔
196
        self.start = 0
1✔
197
        self.stop = 0
1✔
198
        self.error_encoding = False
1✔
199
        self.cutting_start = start or 0
1✔
200
        self.cutting_stop = stop or 0
1✔
201
        self.dressing = dressing or None
1✔
202

203
    def is_video(self):
1✔
204
        """Check if current encoding correspond to a video."""
205
        return len(self.list_video_track) > 0
1✔
206

207
    def get_subtime(self, clip_begin, clip_end):
1✔
208
        subtime = ""
1✔
209
        if clip_begin != 0 or clip_end != 0:
1✔
210
            subtime += "-ss %s " % str(clip_begin) + "-to %s " % str(clip_end)
×
211
        return subtime
1✔
212

213
    def get_video_data(self):
1✔
214
        """Get alls tracks from video source and put it in object passed in parameter."""
215
        msg = "--> get_info_video" + "\n"
1✔
216
        probe_cmd = FFPROBE_GET_INFO % {
1✔
217
            "ffprobe": FFPROBE_CMD,
218
            "select_streams": "",
219
            "source": '"' + self.video_file + '" ',
220
        }
221
        msg += probe_cmd + "\n"
1✔
222
        duration = 0
1✔
223
        info, return_msg = get_info_from_video(probe_cmd)
1✔
224
        msg += json.dumps(info, indent=2)
1✔
225
        msg += " \n"
1✔
226
        msg += return_msg + "\n"
1✔
227
        self.add_encoding_log("probe_cmd", probe_cmd, True, msg)
1✔
228
        try:
1✔
229
            duration = int(float("%s" % info["format"]["duration"]))
1✔
230
        except (RuntimeError, KeyError, AttributeError, ValueError, TypeError) as err:
×
231
            msg = "\nUnexpected error: {0}".format(err)
×
232
            self.add_encoding_log("duration", "", True, msg)
×
233
        if self.cutting_start != 0 or self.cutting_stop != 0:
1✔
234
            duration = self.cutting_stop - self.cutting_start
×
235
        self.duration = duration
1✔
236
        streams = info.get("streams", [])
1✔
237
        for stream in streams:
1✔
238
            self.add_stream(stream)
1✔
239

240
    def fix_duration(self, input_file):
1✔
241
        msg = "--> get_info_video" + "\n"
×
242
        probe_cmd = 'ffprobe -v quiet -show_entries format=duration -hide_banner  \
×
243
                    -of default=noprint_wrappers=1:nokey=1 -print_format json -i \
244
                    "{}"'.format(
245
            input_file
246
        )
247
        info, return_msg = get_info_from_video(probe_cmd)
×
248
        msg += json.dumps(info, indent=2)
×
249
        msg += " \n"
×
250
        msg += return_msg + "\n"
×
251
        duration = 0
×
252
        try:
×
253
            duration = int(float("%s" % info["format"]["duration"]))
×
254
        except (RuntimeError, KeyError, AttributeError, ValueError, TypeError) as err:
×
255
            msg += "\nUnexpected error: {0}".format(err)
×
256
        self.add_encoding_log("fix_duration", "", True, msg)
×
257
        if self.cutting_start != 0 or self.cutting_stop != 0:
×
258
            duration = self.cutting_stop - self.cutting_start
×
259
        self.duration = duration
×
260

261
    def add_stream(self, stream):
1✔
262
        codec_type = stream.get("codec_type", "unknown")
1✔
263
        # https://ffmpeg.org/doxygen/3.2/group__lavu__misc.html#ga9a84bba4713dfced21a1a56163be1f48
264
        if codec_type == "audio":
1✔
265
            codec = stream.get("codec_name", "unknown")
1✔
266
            self.list_audio_track["%s" % stream.get("index")] = {
1✔
267
                "sample_rate": stream.get("sample_rate", 0),
268
                "channels": stream.get("channels", 0),
269
            }
270
        if codec_type == "video":
1✔
271
            codec = stream.get("codec_name", "unknown")
1✔
272
            if any(ext in codec.lower() for ext in image_codec):
1✔
273
                self.list_image_track["%s" % stream.get("index")] = {
×
274
                    "width": stream.get("width", 0),
275
                    "height": stream.get("height", 0),
276
                }
277
            else:
278
                self.list_video_track["%s" % stream.get("index")] = {
1✔
279
                    "width": stream.get("width", 0),
280
                    "height": stream.get("height", 0),
281
                }
282
        if codec_type == "subtitle":
1✔
283
            codec = stream.get("codec_name", "unknown")
×
284
            language = ""
×
285
            if stream.get("tags"):
×
286
                language = stream.get("tags").get("language", "")
×
287
            self.list_subtitle_track["%s" % stream.get("index")] = {"language": language}
×
288

289
    def get_output_dir(self):
1✔
290
        dirname = os.path.dirname(self.video_file)
1✔
291
        return os.path.join(dirname, "%04d" % int(self.id))
1✔
292

293
    def create_output_dir(self):
1✔
294
        output_dir = self.get_output_dir()
1✔
295
        if not os.path.exists(output_dir):
1✔
296
            os.makedirs(output_dir)
1✔
297
        self.output_dir = output_dir
1✔
298

299
    def get_mp4_command(self):
1✔
300
        mp4_command = "%s " % FFMPEG_CMD
1✔
301
        list_rendition = get_list_rendition()
1✔
302
        # remove rendition if encode_mp4 == False
303
        for rend in list_rendition.copy():
1✔
304
            if list_rendition[rend]["encode_mp4"] is False:
1✔
305
                list_rendition.pop(rend)
1✔
306
        if len(list_rendition) == 0:
1✔
307
            return ""
×
308
        first_item = list_rendition.popitem(last=False)
1✔
309
        mp4_command += FFMPEG_INPUT % {
1✔
310
            "input": self.video_file,
311
            "nb_threads": FFMPEG_NB_THREADS,
312
        }
313
        output_file = os.path.join(self.output_dir, "%sp.mp4" % first_item[0])
1✔
314
        mp4_command += FFMPEG_MP4_ENCODE % {
1✔
315
            "cut": self.get_subtime(self.cutting_start, self.cutting_stop),
316
            "map_audio": "-map 0:a:0" if len(self.list_audio_track) > 0 else "",
317
            "libx": FFMPEG_LIBX,
318
            "height": first_item[0],
319
            "preset": FFMPEG_PRESET,
320
            "profile": FFMPEG_PROFILE,
321
            "level": FFMPEG_LEVEL,
322
            "crf": FFMPEG_CRF,
323
            "maxrate": first_item[1]["maxrate"],
324
            "bufsize": first_item[1]["maxrate"],
325
            "ba": first_item[1]["audio_bitrate"],
326
            "output": output_file,
327
        }
328
        self.list_mp4_files[first_item[0]] = output_file
1✔
329
        """
330
        il est possible de faire ainsi :
331
        mp4_command += FFMPEG_MP4_ENCODE.format(
332
            height=first_item[0],
333
            [...]
334
        )
335
        """
336
        in_height = list(self.list_video_track.items())[0][1]["height"]
1✔
337
        for rend in list_rendition:
1✔
338
            resolution_threshold = rend - rend * (
1✔
339
                list_rendition[rend]["encoding_resolution_threshold"] / 100
340
            )
341
            if in_height >= resolution_threshold:
1✔
342
                output_file = os.path.join(self.output_dir, "%sp.mp4" % rend)
×
343
                mp4_command += FFMPEG_MP4_ENCODE % {
×
344
                    "cut": self.get_subtime(self.cutting_start, self.cutting_stop),
345
                    "map_audio": "-map 0:a:0" if len(self.list_audio_track) > 0 else "",
346
                    "libx": FFMPEG_LIBX,
347
                    "height": min(rend, in_height),
348
                    "preset": FFMPEG_PRESET,
349
                    "profile": FFMPEG_PROFILE,
350
                    "level": FFMPEG_LEVEL,
351
                    "crf": FFMPEG_CRF,
352
                    "maxrate": list_rendition[rend]["maxrate"],
353
                    "bufsize": list_rendition[rend]["maxrate"],
354
                    "ba": list_rendition[rend]["audio_bitrate"],
355
                    "output": output_file,
356
                }
357
                self.list_mp4_files[rend] = output_file
×
358
        return mp4_command
1✔
359

360
    def get_hls_command(self):
1✔
361
        hls_command = "%s " % FFMPEG_CMD
1✔
362
        list_rendition = get_list_rendition()
1✔
363
        hls_command += FFMPEG_INPUT % {
1✔
364
            "input": self.video_file,
365
            "nb_threads": FFMPEG_NB_THREADS,
366
        }
367
        hls_common_params = FFMPEG_HLS_COMMON_PARAMS % {
1✔
368
            "cut": self.get_subtime(self.cutting_start, self.cutting_stop),
369
            "libx": FFMPEG_LIBX,
370
            "preset": FFMPEG_PRESET,
371
            "profile": FFMPEG_PROFILE,
372
            "level": FFMPEG_LEVEL,
373
            "crf": FFMPEG_CRF,
374
        }
375
        hls_command += hls_common_params
1✔
376
        in_height = list(self.list_video_track.items())[0][1]["height"]
1✔
377
        for index, rend in enumerate(list_rendition):
1✔
378
            resolution_threshold = rend - rend * (
1✔
379
                list_rendition[rend]["encoding_resolution_threshold"] / 100
380
            )
381
            if in_height >= resolution_threshold or index == 0:
1✔
382
                output_file = os.path.join(self.output_dir, "%sp.m3u8" % rend)
1✔
383
                hls_command += hls_common_params
1✔
384
                hls_command += FFMPEG_HLS_ENCODE_PARAMS % {
1✔
385
                    "height": min(rend, in_height),
386
                    "maxrate": list_rendition[rend]["maxrate"],
387
                    "bufsize": list_rendition[rend]["maxrate"],
388
                    "ba": list_rendition[rend]["audio_bitrate"],
389
                    "hls_time": FFMPEG_HLS_TIME,
390
                    "output": output_file,
391
                }
392
                self.list_hls_files[rend] = output_file
1✔
393
        return hls_command
1✔
394

395
    def get_dressing_file(self):
1✔
396
        """Create or replace the dressed video file."""
NEW
397
        dirname = os.path.dirname(self.video_file)
×
NEW
398
        filename, ext = os.path.splitext(os.path.basename(self.video_file))
×
NEW
399
        output_file = os.path.join(dirname, filename + "_dressing" + ext)
×
NEW
400
        return output_file
×
401

402
    def get_dressing_command(self):
1✔
403
        """Get the command based on the dressing object parameters"""
NEW
404
        height = str(list(self.list_video_track.items())[0][1]["height"])
×
NEW
405
        order_opening_credits = 0
×
NEW
406
        dressing_command_params = '[vid][0:a]'
×
NEW
407
        number_concat = 1
×
NEW
408
        dressing_command = "%s " % FFMPEG_CMD
×
NEW
409
        dressing_command += FFMPEG_INPUT % {
×
410
            "input": self.video_file,
411
            "nb_threads": FFMPEG_NB_THREADS,
412
        }
413

NEW
414
        dressing_command += get_dressing_input(self.dressing, FFMPEG_DRESSING_INPUT)
×
NEW
415
        dressing_command_filter = []
×
NEW
416
        dressing_command_filter.append(FFMPEG_DRESSING_SCALE % {
×
417
            "number": "0",
418
            "height": height,
419
            "name": "vid",
420
        })
NEW
421
        if self.dressing.watermark:
×
NEW
422
            dressing_command_params = '[video][0:a]'
×
NEW
423
            order_opening_credits = order_opening_credits + 1
×
NEW
424
            name_out = ""
×
NEW
425
            if self.dressing.opening_credits or self.dressing.ending_credits:
×
NEW
426
                name_out = "[video]"
×
NEW
427
            dressing_command_filter.append(FFMPEG_DRESSING_WATERMARK % {
×
428
                "opacity": self.dressing.opacity / 100.0,
429
                "position": get_position_value(self.dressing.position, height),
430
                "name_out": name_out,
431
            })
NEW
432
        if self.dressing.opening_credits:
×
NEW
433
            order_opening_credits = order_opening_credits + 1
×
NEW
434
            number_concat = number_concat + 1
×
NEW
435
            dressing_command_params = ('[debut][' + str(order_opening_credits)
×
436
                                       + ':a]' + dressing_command_params)
NEW
437
            dressing_command_filter.append(FFMPEG_DRESSING_SCALE % {
×
438
                "number": str(order_opening_credits),
439
                "height": height,
440
                "name": "debut",
441
            })
NEW
442
        if self.dressing.ending_credits:
×
NEW
443
            number_concat = number_concat + 1
×
NEW
444
            dressing_command_params = (dressing_command_params + '[fin]['
×
445
                                       + str(order_opening_credits + 1) + ':a]')
NEW
446
            dressing_command_filter.append(FFMPEG_DRESSING_SCALE % {
×
447
                "number": str(order_opening_credits + 1),
448
                "height": str(list(self.list_video_track.items())[0][1]["height"]),
449
                "name": "fin",
450
            })
NEW
451
        if self.dressing.opening_credits or self.dressing.ending_credits:
×
NEW
452
            dressing_command_filter.append(FFMPEG_DRESSING_CONCAT % {
×
453
                "params": dressing_command_params,
454
                "number": number_concat,
455
            })
456

NEW
457
        dressing_command += FFMPEG_DRESSING_FILTER_COMPLEX % {
×
458
            "filter": ';'.join(dressing_command_filter),
459
        }
460

NEW
461
        if self.dressing.opening_credits or self.dressing.ending_credits:
×
NEW
462
            dressing_command += " -map '[v]' -map '[a]' "
×
463

NEW
464
        output_file = self.get_dressing_file()
×
NEW
465
        dressing_command += FFMPEG_DRESSING_OUTPUT % {
×
466
            "output": output_file,
467
        }
NEW
468
        return dressing_command
×
469

470
    def encode_video_dressing(self):
1✔
471
        """Encode the dressed video."""
NEW
472
        dressing_command = self.get_dressing_command()
×
NEW
473
        return_value, return_msg = launch_cmd(dressing_command)
×
NEW
474
        self.add_encoding_log("dressing_command", dressing_command, return_value,
×
475
                              return_msg)
NEW
476
        self.video_file = self.get_dressing_file()
×
477

478
    def encode_video_part(self):
1✔
479
        mp4_command = self.get_mp4_command()
1✔
480
        return_value, return_msg = launch_cmd(mp4_command)
1✔
481
        self.add_encoding_log("mp4_command", mp4_command, return_value, return_msg)
1✔
482
        if not return_value:
1✔
483
            self.error_encoding = True
×
484
        if self.duration == 0:
1✔
485
            list_rendition = get_list_rendition()
×
486
            first_item = list_rendition.popitem(last=False)
×
487
            self.fix_duration(self.list_mp4_files[first_item[0]])
×
488
        hls_command = self.get_hls_command()
1✔
489
        return_value, return_msg = launch_cmd(hls_command)
1✔
490
        if return_value:
1✔
491
            self.create_main_livestream()
1✔
492
        self.add_encoding_log("hls_command", hls_command, return_value, return_msg)
1✔
493

494
    def create_main_livestream(self):
1✔
495
        list_rendition = get_list_rendition()
1✔
496
        livestream_content = ""
1✔
497
        for index, rend in enumerate(list_rendition):
1✔
498
            rend_livestream = os.path.join(
1✔
499
                self.get_output_dir(), "livestream%s.m3u8" % rend
500
            )
501
            if os.path.exists(rend_livestream):
1✔
502
                with open(rend_livestream, "r") as file:
1✔
503
                    data = file.read()
1✔
504
                if index == 0:
1✔
505
                    livestream_content += data
1✔
506
                else:
507
                    livestream_content += "\n".join(data.split("\n")[2:])
×
508
                os.remove(rend_livestream)
1✔
509
        livestream_file = open(
1✔
510
            os.path.join(self.get_output_dir(), "livestream.m3u8"), "w"
511
        )
512
        livestream_file.write(livestream_content.replace("\n\n", "\n"))
1✔
513
        livestream_file.close()
1✔
514

515
    def get_mp3_command(self):
1✔
516
        mp3_command = "%s " % FFMPEG_CMD
1✔
517
        mp3_command += FFMPEG_INPUT % {
1✔
518
            "input": self.video_file,
519
            "nb_threads": FFMPEG_NB_THREADS,
520
        }
521
        output_file = os.path.join(self.output_dir, "audio_%s.mp3" % FFMPEG_AUDIO_BITRATE)
1✔
522
        mp3_command += FFMPEG_MP3_ENCODE % {
1✔
523
            # "audio_bitrate": AUDIO_BITRATE,
524
            "cut": self.get_subtime(self.cutting_start, self.cutting_stop),
525
            "output": output_file,
526
        }
527
        self.list_mp3_files[FFMPEG_AUDIO_BITRATE] = output_file
1✔
528
        return mp3_command
1✔
529

530
    def get_m4a_command(self):
1✔
531
        m4a_command = "%s " % FFMPEG_CMD
1✔
532
        m4a_command += FFMPEG_INPUT % {
1✔
533
            "input": self.video_file,
534
            "nb_threads": FFMPEG_NB_THREADS,
535
        }
536
        output_file = os.path.join(self.output_dir, "audio_%s.m4a" % FFMPEG_AUDIO_BITRATE)
1✔
537
        m4a_command += FFMPEG_M4A_ENCODE % {
1✔
538
            "cut": self.get_subtime(self.cutting_start, self.cutting_stop),
539
            "audio_bitrate": FFMPEG_AUDIO_BITRATE,
540
            "output": output_file,
541
        }
542
        self.list_m4a_files[FFMPEG_AUDIO_BITRATE] = output_file
1✔
543
        return m4a_command
1✔
544

545
    def encode_audio_part(self):
1✔
546
        mp3_command = self.get_mp3_command()
1✔
547
        return_value, return_msg = launch_cmd(mp3_command)
1✔
548
        self.add_encoding_log("mp3_command", mp3_command, return_value, return_msg)
1✔
549
        if self.duration == 0:
1✔
550
            new_k = list(self.list_mp3_files)[0]
×
551
            self.fix_duration(self.list_mp3_files[new_k])
×
552
        if not self.is_video():
1✔
553
            m4a_command = self.get_m4a_command()
1✔
554
            return_value, return_msg = launch_cmd(m4a_command)
1✔
555
            self.add_encoding_log("m4a_command", m4a_command, return_value, return_msg)
1✔
556

557
    def get_extract_thumbnail_command(self):
1✔
558
        thumbnail_command = "%s " % FFMPEG_CMD
×
559
        thumbnail_command += FFMPEG_INPUT % {
×
560
            "input": self.video_file,
561
            "nb_threads": FFMPEG_NB_THREADS,
562
        }
563
        for img in self.list_image_track:
×
564
            output_file = os.path.join(self.output_dir, "thumbnail_%s.png" % img)
×
565
            thumbnail_command += FFMPEG_EXTRACT_THUMBNAIL % {
×
566
                "index": img,
567
                "output": output_file,
568
            }
569
            self.list_thumbnail_files[img] = output_file
×
570
        return thumbnail_command
×
571

572
    def get_create_thumbnail_command(self):
1✔
573
        thumbnail_command = "%s " % FFMPEG_CMD
1✔
574
        first_item = self.get_first_item()
1✔
575
        input_file = self.list_mp4_files[first_item[0]]
1✔
576
        thumbnail_command += FFMPEG_INPUT % {
1✔
577
            "input": input_file,
578
            "nb_threads": FFMPEG_NB_THREADS,
579
        }
580
        output_file = os.path.join(self.output_dir, "thumbnail")
1✔
581
        thumbnail_command += FFMPEG_CREATE_THUMBNAIL % {
1✔
582
            "duration": self.duration,
583
            "nb_thumbnail": FFMPEG_NB_THUMBNAIL,
584
            "output": output_file,
585
        }
586
        for nb in range(0, FFMPEG_NB_THUMBNAIL):
1✔
587
            num_thumb = str(nb + 1)
1✔
588
            self.list_thumbnail_files[num_thumb] = "%s_000%s.png" % (
1✔
589
                output_file,
590
                num_thumb,
591
            )
592
        return thumbnail_command
1✔
593

594
    def get_first_item(self):
1✔
595
        """get the first mp4 render from setting"""
596
        list_rendition = get_list_rendition()
1✔
597
        for rend in list_rendition.copy():
1✔
598
            if list_rendition[rend]["encode_mp4"] is False:
1✔
599
                list_rendition.pop(rend)
1✔
600
        if len(list_rendition) == 0:
1✔
601
            return None
×
602
        else:
603
            return list_rendition.popitem(last=False)
1✔
604

605
    def create_overview(self):
1✔
606
        first_item = self.get_first_item()
1✔
607
        # overview combine for 160x90
608
        in_height = list(self.list_video_track.items())[0][1]["height"]
1✔
609
        in_width = list(self.list_video_track.items())[0][1]["width"]
1✔
610
        image_height = 75  # decrease to 75 px instead of 90 due to montage overflow
1✔
611
        coef = in_height / image_height
1✔
612
        image_width = int(in_width / coef)
1✔
613
        input_file = self.list_mp4_files[first_item[0]]
1✔
614
        nb_img = 100
1✔
615
        step = 1
1✔
616
        if self.duration < 100:
1✔
617
            # nb_img = int(self.duration * 10 / 100)
618
            step = 10  # on ne fait que 10 images si la video dure moins de 100 sec.
1✔
619
        overviewimagefilename = os.path.join(self.output_dir, "overview.png")
1✔
620
        image_url = os.path.basename(overviewimagefilename)
1✔
621
        overviewfilename = os.path.join(self.output_dir, "overview.vtt")
1✔
622
        webvtt = WebVTT()
1✔
623
        for i in range(0, nb_img, step):
1✔
624
            # create overviewimagefilename for first pass
625
            output_file = (
1✔
626
                os.path.join(self.output_dir, "thumbnail_%s.png" % i)
627
                if i > 0
628
                else overviewimagefilename
629
            )
630
            cmd_ffmpegthumbnailer = (
1✔
631
                'ffmpegthumbnailer -t "%(stamp)s" '
632
                + '-s "%(image_width)s" -i %(source)s -c png '
633
                + "-o %(output_file)s "
634
            ) % {
635
                "stamp": str(i) + "%",
636
                "source": input_file,
637
                "output_file": output_file,
638
                "image_width": image_width,
639
            }
640
            return_value, return_msg = launch_cmd(cmd_ffmpegthumbnailer)
1✔
641
            # self.add_encoding_log(
642
            # "ffmpegthumbnailer_%s" % i, cmd_ffmpegthumbnailer, return_value, return_msg)
643
            if return_value and check_file(output_file) and i > 0:
1✔
644
                # print("MONTAGE")
645
                cmd_montage = (
1✔
646
                    "montage -geometry +0+0 %(overviewimagefilename)s \
647
                    %(output_file)s  %(overviewimagefilename)s"
648
                    % {
649
                        "overviewimagefilename": overviewimagefilename,
650
                        "output_file": output_file,
651
                    }
652
                )
653
                return_value, return_msg = launch_cmd(cmd_montage)
1✔
654
                if not return_value:
1✔
655
                    print("cmd_montage_%s" % i, cmd_montage, return_value, return_msg)
×
656
                # self.add_encoding_log
657
                # ("cmd_montage_%s" % i, cmd_montage, return_value, return_msg)
658
                os.remove(output_file)
1✔
659
            start = format(float(self.duration * i / 100), ".3f")
1✔
660
            end = format(float(self.duration * (i + step) / 100), ".3f")
1✔
661
            start_time = time.strftime(
1✔
662
                "%H:%M:%S", time.gmtime(int(str(start).split(".")[0]))
663
            )
664
            start_time += ".%s" % (str(start).split(".")[1])
1✔
665
            end_time = time.strftime(
1✔
666
                "%H:%M:%S", time.gmtime(int(str(end).split(".")[0]))
667
            ) + ".%s" % (str(end).split(".")[1])
668
            caption = Caption(
1✔
669
                "%s" % start_time,
670
                "%s" % end_time,
671
                "%s#xywh=%d,%d,%d,%d"
672
                % (image_url, image_width * (i / step), 0, image_width, image_height),
673
            )
674
            webvtt.captions.append(caption)
1✔
675
        webvtt.save(overviewfilename)
1✔
676
        if check_file(overviewfilename) and check_file(overviewimagefilename):
1✔
677
            self.list_overview_files["0"] = overviewimagefilename
1✔
678
            self.list_overview_files["1"] = overviewfilename
1✔
679
            # self.encoding_log += "\n- overviewfilename:\n%s" % overviewfilename
680
        else:
681
            self.add_encoding_log("create_overview", "", False, "")
×
682

683
    def encode_image_part(self):
1✔
684
        if len(self.list_image_track) > 0:
1✔
685
            thumbnail_command = self.get_extract_thumbnail_command()
×
686
            return_value, return_msg = launch_cmd(thumbnail_command)
×
687
            self.add_encoding_log(
×
688
                "extract_thumbnail_command", thumbnail_command, return_value, return_msg
689
            )
690
        elif self.is_video():
1✔
691
            thumbnail_command = self.get_create_thumbnail_command()
1✔
692
            return_value, return_msg = launch_cmd(thumbnail_command)
1✔
693
            self.add_encoding_log(
1✔
694
                "create_thumbnail_command", thumbnail_command, return_value, return_msg
695
            )
696
        # on ne fait pas d'overview pour les videos de moins de 10 secondes
697
        # (laisser les 10sec inclus pour laisser les tests passer) --> OK
698
        if self.is_video() and self.duration >= 10:
1✔
699
            self.create_overview()
1✔
700

701
    def get_extract_subtitle_command(self):
1✔
702
        subtitle_command = "%s " % FFMPEG_CMD
×
703
        subtitle_command += FFMPEG_INPUT % {
×
704
            "input": self.video_file,
705
            "nb_threads": FFMPEG_NB_THREADS,
706
        }
707
        for sub in self.list_subtitle_track:
×
708
            lang = self.list_subtitle_track[sub]["language"]
×
709
            output_file = os.path.join(self.output_dir, "subtitle_%s.vtt" % lang)
×
710
            subtitle_command += FFMPEG_EXTRACT_SUBTITLE % {
×
711
                "index": sub,
712
                "output": output_file,
713
            }
714
            self.list_subtitle_files[sub] = [lang, output_file]
×
715
        return subtitle_command
×
716

717
    def get_subtitle_part(self):
1✔
718
        if len(self.list_subtitle_track) > 0:
×
719
            subtitle_command = self.get_extract_subtitle_command()
×
720
            return_value, return_msg = launch_cmd(subtitle_command)
×
721
            self.add_encoding_log(
×
722
                "subtitle_command", subtitle_command, return_value, return_msg
723
            )
724

725
    def export_to_json(self):
1✔
726
        data_to_dump = {}
1✔
727
        for attribute, value in self.__dict__.items():
1✔
728
            if attribute == 'dressing' and value is not None:
1✔
NEW
729
                data_to_dump[attribute] = value.to_json()
×
730
            else:
731
                data_to_dump[attribute] = value
1✔
732
        with open(self.output_dir + "/info_video.json", "w") as outfile:
1✔
733
            json.dump(data_to_dump, outfile, indent=2)
1✔
734

735
    def add_encoding_log(self, title, command, result, msg):
1✔
736
        self.encoding_log[title] = {"command": command, "result": result, "msg": msg}
1✔
737
        if result is False and self.error_encoding is False:
1✔
738
            self.error_encoding = True
1✔
739

740
    def start_encode(self):
1✔
741
        self.start = time.ctime()
1✔
742
        self.create_output_dir()
1✔
743
        self.get_video_data()
1✔
744
        if self.dressing is not None:
1✔
NEW
745
            self.encode_video_dressing()
×
746
        print(self.id, self.video_file, self.duration)
1✔
747
        if self.is_video():
1✔
748
            self.encode_video_part()
1✔
749
        if len(self.list_audio_track) > 0:
1✔
750
            self.encode_audio_part()
1✔
751
        self.encode_image_part()
1✔
752
        if len(self.list_subtitle_track) > 0:
1✔
753
            self.get_subtitle_part()
×
754
        self.stop = time.ctime()
1✔
755
        self.export_to_json()
1✔
756

757

758
def fix_input(input):
1✔
759
    filename = ""
×
760
    if args.input.startswith("/"):
×
761
        path_file = args.input
×
762
    else:
763
        path_file = os.path.join(os.getcwd(), args.input)
×
764
    if os.access(path_file, os.F_OK) and os.stat(path_file).st_size > 0:
×
765
        # remove accent and space
766
        filename = "".join(
×
767
            (
768
                c
769
                for c in unicodedata.normalize("NFD", path_file)
770
                if unicodedata.category(c) != "Mn"
771
            )
772
        )
773
        filename = filename.replace(" ", "_")
×
774
        os.rename(
×
775
            path_file,
776
            filename,
777
        )
778
        print("Encoding file {} \n".format(filename))
×
779
    return filename
×
780

781

782
"""
783
  remote encode ???
784
"""
785
if __name__ == "__main__":
1✔
786
    start = "Start at: %s" % time.ctime()
×
787
    parser = argparse.ArgumentParser(description="Running encoding video.")
×
788
    parser.add_argument("--id", required=True, help="the ID of the video")
×
789
    parser.add_argument("--start", required=False, help="Start cut")
×
790
    parser.add_argument("--stop", required=False, help="Stop cut")
×
791
    parser.add_argument("--input", required=True, help="name of input file to encode")
×
NEW
792
    parser.add_argument("--dressing", required=False, help="Dressing for the video")
×
793

794
    args = parser.parse_args()
×
795
    print(args.start)
×
796
    filename = fix_input(args.input)
×
NEW
797
    encoding_video = Encoding_video(args.id, filename, args.start, args.stop,
×
798
                                    args.dressing)
799
    # error if uncommented
800
    # encoding_video.encoding_log += start
801
    # AttributeError: 'NoneType' object has no attribute 'get'
802
    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

© 2026 Coveralls, Inc