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

EsupPortail / Esup-Pod / 28237579315

26 Jun 2026 12:19PM UTC coverage: 69.176%. First build
28237579315

Pull #1427

github

web-flow
Fix FFMPEG commands by adding spaces (#1472)

Avoid errors like :

```
Error opening input: No such file or directory
Error opening input file /data/www/poduser/media/videos/xxx/yyy/360p.mp4-vf.
```

(here, the `-vf` corrrespond to options given to FFMPEG command)
Pull Request #1427: [Release] 4.3.0

798 of 1112 new or added lines in 24 files covered. (71.76%)

13676 of 19770 relevant lines covered (69.18%)

0.69 hits per line

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

77.24
/pod/video_encode_transcript/encoding_utils.py
1
import json
1✔
2
import logging
1✔
3
import os
1✔
4
import shlex
1✔
5
import subprocess
1✔
6
from collections import OrderedDict
1✔
7
from timeit import default_timer as timer
1✔
8

9
try:
1✔
10
    from .encoding_settings import VIDEO_RENDITIONS
1✔
11
except (ImportError, ValueError):
×
12
    from encoding_settings import VIDEO_RENDITIONS
×
13

14
try:
1✔
15
    from django.conf import settings
1✔
16

17
    VIDEO_RENDITIONS = getattr(settings, "VIDEO_RENDITIONS", VIDEO_RENDITIONS)
1✔
18
    DEBUG = getattr(settings, "DEBUG", True)
1✔
19
    MEDIA_ROOT = getattr(settings, "MEDIA_ROOT", "")
1✔
20
    DEFAULT_RECORDER_PATH = getattr(settings, "DEFAULT_RECORDER_PATH", "")
1✔
21
except ImportError:  # pragma: no cover
22
    DEBUG = True
23
    MEDIA_ROOT = ""
24
    DEFAULT_RECORDER_PATH = ""
25

26
logger = logging.getLogger(__name__)
1✔
27
if DEBUG:
1✔
28
    logger.setLevel(logging.DEBUG)
1✔
29

30

31
def sec_to_timestamp(total_seconds) -> str:
1✔
32
    """Format time for webvtt caption."""
33
    if total_seconds < 0:
1✔
34
        total_seconds = 0
1✔
35
    hours = int(total_seconds // 3600)
1✔
36
    minutes = int((total_seconds % 3600) // 60)
1✔
37
    seconds = total_seconds % 60
1✔
38
    return "{:02d}:{:02d}:{:06.3f}".format(hours, minutes, seconds)
1✔
39

40

41
def get_dressing_position_value(position: str, height: str) -> str:
1✔
42
    """
43
    Obtain dimensions proportional to the video format.
44

45
    Args:
46
        position (str): property "position" of the dressing object.
47
        height (str): height of the source video.
48

49
    Returns:
50
        str: params for the ffmpeg command.
51
    """
52
    height = str(float(height) * 0.05)
1✔
53
    if position == "top_right":
1✔
54
        return "overlay=main_w-overlay_w-" + height + ":" + height
1✔
55
    elif position == "top_left":
1✔
56
        return "overlay=" + height + ":" + height
1✔
57
    elif position == "bottom_right":
1✔
58
        return "overlay=main_w-overlay_w-" + height + ":main_h-overlay_h-" + height
1✔
59
    elif position == "bottom_left":
1✔
60
        return "overlay=" + height + ":main_h-overlay_h-" + height
1✔
61
    return ""
×
62

63

64
def get_renditions():
1✔
65
    try:
1✔
66
        from django.core import serializers
1✔
67

68
        from .models import VideoRendition
1✔
69

70
        renditions = json.loads(
1✔
71
            serializers.serialize("json", VideoRendition.objects.all())
72
        )
73
        video_rendition = []
1✔
74
        for rend in renditions:
1✔
75
            video_rendition.append(rend["fields"])
1✔
76
        return video_rendition
1✔
77
    except ImportError:
×
78
        return VIDEO_RENDITIONS
×
79

80

81
def _is_safe_file_path(path_file) -> bool:
1✔
82
    """Return whether a path resolves inside an authorized local root.
83

84
    Authorized roots are ``MEDIA_ROOT``, ``DEFAULT_RECORDER_PATH`` and ``/tmp``.
85
    """
86
    if not path_file:
1✔
87
        return False
1✔
88
    try:
1✔
89
        candidate = os.path.realpath(os.fspath(path_file))
1✔
NEW
90
    except (TypeError, ValueError, OSError):
×
NEW
91
        return False
×
92

93
    allowed_roots = [MEDIA_ROOT, DEFAULT_RECORDER_PATH, "/tmp"]
1✔
94
    for root in allowed_roots:
1✔
95
        if not root:
1✔
96
            continue
1✔
97
        root_real = os.path.realpath(root)
1✔
98
        try:
1✔
99
            if os.path.commonpath([candidate, root_real]) == root_real:
1✔
100
                return True
1✔
NEW
101
        except ValueError:
×
NEW
102
            continue
×
103
    return False
1✔
104

105

106
def check_file(path_file) -> bool:
1✔
107
    """Return whether a path is authorized, existing, and non-empty."""
108
    if not _is_safe_file_path(path_file):
1✔
109
        return False
1✔
110
    safe_path = os.path.realpath(os.fspath(path_file))
1✔
111
    try:
1✔
112
        return (
1✔
113
            os.path.isfile(safe_path)
114
            and os.access(safe_path, os.F_OK)
115
            and os.stat(safe_path).st_size > 0
116
        )
NEW
117
    except (OSError, ValueError):
×
NEW
118
        return False
×
119

120

121
def get_list_rendition():
1✔
122
    list_rendition = {}
1✔
123
    renditions = get_renditions()
1✔
124
    for rend in renditions:
1✔
125
        list_rendition[int(rend["resolution"].split("x")[1])] = rend
1✔
126
    list_rendition = OrderedDict(sorted(list_rendition.items(), key=lambda t: t[0]))
1✔
127
    return list_rendition
1✔
128

129

130
def get_info_from_video(probe_cmd):
1✔
131
    info = None
1✔
132
    msg = ""
1✔
133
    try:
1✔
134
        output = subprocess.check_output(shlex.split(probe_cmd), stderr=subprocess.PIPE)
1✔
135
        info = json.loads(output)
1✔
136
    except subprocess.CalledProcessError as e:
×
137
        # raise RuntimeError('ffprobe returned non-zero status: {}'.format(
138
        # e.stderr))
139
        msg += 20 * "////" + "\n"
×
140
        msg += "Runtime Error: {0}\n".format(e)
×
141
    except OSError as err:
×
142
        # raise OSError(e.errno, 'ffprobe not found: {}'.format(e.strerror))
143
        msg += 20 * "////" + "\n"
×
144
        msg += "OS error: {0}\n".format(err)
×
145
    return info, msg
1✔
146

147

148
def launch_cmd(cmd):
1✔
149
    if cmd == "":
1✔
150
        msg = "No cmd to launch"
×
151
        logger.warning(msg)
×
152
        return False, msg
×
153
    msg = ""
1✔
154
    encode_start = timer()
1✔
155
    return_value = False
1✔
156
    try:
1✔
157
        logger.debug("launch_cmd: %s" % cmd)
1✔
158
        output = subprocess.run(
1✔
159
            shlex.split(cmd),
160
            stdout=subprocess.PIPE,
161
            stderr=subprocess.STDOUT,
162
        )
163
        encode_end = timer() - encode_start
1✔
164
        msg += cmd + "\n"
1✔
165
        msg += "Encode file in {:.3}s.\n".format(encode_end)
1✔
166
        # msg += "\n".join(output.stdout.decode().split('\n'))
167
        try:
1✔
168
            msg += output.stdout.decode("utf-8")
1✔
169
        except UnicodeDecodeError:
×
170
            pass
×
171
        msg += "\n"
1✔
172
        if output.returncode != 0:
1✔
173
            msg += "ERROR RETURN CODE %s for command %s" % (output.returncode, cmd)
×
174
        else:
175
            return_value = True
1✔
176
    except (subprocess.CalledProcessError, OSError) as e:
×
177
        # raise RuntimeError('ffmpeg returned non-zero status: {}'.format(
178
        # e.stderr))
179
        msg += 20 * "////" + "\n"
×
180
        err_msg = "Runtime or OS Error: {0}\n".format(e)
×
181
        msg += err_msg
×
182
        logger.error(err_msg)
×
183
    return return_value, msg
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

© 2026 Coveralls, Inc