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

EsupPortail / Esup-Pod / 8017784387

23 Feb 2024 10:21AM UTC coverage: 70.319% (-0.2%) from 70.49%
8017784387

Pull #1056

github

web-flow
Merge 1c868b2a3 into f8e2c5049
Pull Request #1056: [WIP] Fix remote encoding and other stuff

46 of 156 new or added lines in 9 files covered. (29.49%)

6 existing lines in 6 files now uncovered.

9884 of 14056 relevant lines covered (70.32%)

0.7 hits per line

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

31.69
/pod/video_encode_transcript/transcript.py
1
"""Esup-Pod transcript video functions."""
2

3
from django.conf import settings
1✔
4
from django.core.files import File
1✔
5
from pod.completion.models import Track
1✔
6
from pod.main.tasks import task_start_transcript
1✔
7
from webvtt import Caption
1✔
8

9
from .utils import (
1✔
10
    send_email,
11
    send_email_transcript,
12
    change_encoding_step,
13
    add_encoding_log,
14
)
15
from ..video.models import Video
1✔
16
import importlib.util
1✔
17

18
if (
1✔
19
    importlib.util.find_spec("vosk") is not None
20
    or importlib.util.find_spec("stt") is not None
21
    or importlib.util.find_spec("whisper") is not None
22
):
23
    from .transcript_model import start_transcripting
×
24

25

26
from .encoding_utils import sec_to_timestamp
1✔
27

28
import os
1✔
29
import time
1✔
30

31
from tempfile import NamedTemporaryFile
1✔
32

33
import threading
1✔
34
import logging
1✔
35

36
DEBUG = getattr(settings, "DEBUG", False)
1✔
37

38
if getattr(settings, "USE_PODFILE", False):
1✔
39
    __FILEPICKER__ = True
1✔
40
    from pod.podfile.models import CustomFileModel
1✔
41
    from pod.podfile.models import UserFolder
1✔
42
else:
43
    __FILEPICKER__ = False
×
44
    from pod.main.models import CustomFileModel
×
45

46
EMAIL_ON_TRANSCRIPTING_COMPLETION = getattr(
1✔
47
    settings, "EMAIL_ON_TRANSCRIPTING_COMPLETION", True
48
)
49
TRANSCRIPTION_MODEL_PARAM = getattr(settings, "TRANSCRIPTION_MODEL_PARAM", False)
1✔
50
USE_TRANSCRIPTION = getattr(settings, "USE_TRANSCRIPTION", False)
1✔
51
if USE_TRANSCRIPTION:
1✔
52
    TRANSCRIPTION_TYPE = getattr(settings, "TRANSCRIPTION_TYPE", "STT")
1✔
53
TRANSCRIPTION_NORMALIZE = getattr(settings, "TRANSCRIPTION_NORMALIZE", False)
1✔
54
CELERY_TO_ENCODE = getattr(settings, "CELERY_TO_ENCODE", False)
1✔
55

56
USE_REMOTE_ENCODING_TRANSCODING = getattr(
1✔
57
    settings, "USE_REMOTE_ENCODING_TRANSCODING", False
58
)
59
if USE_REMOTE_ENCODING_TRANSCODING:
1✔
60
    from .transcripting_tasks import start_transcripting_task
×
61

62
log = logging.getLogger(__name__)
1✔
63

64
"""
65
TO TEST IN THE SHELL -->
66
from pod.video.transcript import *
67
stt_model = get_model("fr")
68
msg, webvtt, all_text = main_stt_transcript(
69
    "/test/audio_192k_pod.mp3", # file
70
    177, # file duration
71
    stt_model # model stt loaded
72
)
73
print(webvtt)
74
"""
75

76

77
# ##########################################################################
78
# TRANSCRIPT VIDEO: THREAD TO LAUNCH TRANSCRIPT
79
# ##########################################################################
80
def start_transcript(video_id, threaded=True):
1✔
81
    """
82
    Call to start transcript main function.
83

84
    Will launch transcript mode depending on configuration.
85
    """
86
    if threaded:
1✔
87
        if CELERY_TO_ENCODE:
1✔
88
            task_start_transcript.delay(video_id)
×
89
        else:
90
            log.info("START TRANSCRIPT VIDEO %s" % video_id)
1✔
91
            t = threading.Thread(target=main_threaded_transcript, args=[video_id])
1✔
92
            t.setDaemon(True)
1✔
93
            t.start()
1✔
94
    else:
95
        main_threaded_transcript(video_id)
×
96

97

98
def main_threaded_transcript(video_to_encode_id):
1✔
99
    """
100
    Transcript main function.
101

102
    Will check all configuration and file and launch transcript.
103
    """
104
    change_encoding_step(video_to_encode_id, 5, "transcripting audio")
1✔
105

106
    video_to_encode = Video.objects.get(id=video_to_encode_id)
×
107

108
    msg = ""
×
109
    lang = video_to_encode.transcript
×
110
    # check if TRANSCRIPTION_MODEL_PARAM [lang] exist
111
    if not TRANSCRIPTION_MODEL_PARAM[TRANSCRIPTION_TYPE].get(lang):
×
112
        msg += "\n no stt model found for lang: %s." % lang
×
113
        msg += "Please add it in TRANSCRIPTION_MODEL_PARAM."
×
114
        change_encoding_step(video_to_encode.id, -1, msg)
×
115
        send_email(msg, video_to_encode.id)
×
116
    else:
117
        mp3file = (
×
118
            video_to_encode.get_video_mp3().source_file
119
            if video_to_encode.get_video_mp3()
120
            else None
121
        )
122
        if mp3file is None:
×
123
            msg += "\n no mp3 file found for video: %s." % video_to_encode.id
×
124
            change_encoding_step(video_to_encode.id, -1, msg)
×
125
            send_email(msg, video_to_encode.id)
×
126
        else:
127
            mp3filepath = mp3file.path
×
NEW
128
            if USE_REMOTE_ENCODING_TRANSCODING:
×
129
                start_transcripting_task.delay(
×
130
                    video_to_encode.id, mp3filepath, video_to_encode.duration, lang
131
                )
132
            else:
133
                msg, webvtt = start_transcripting(
×
134
                    mp3filepath, video_to_encode.duration, lang
135
                )
136
                save_vtt_and_notify(video_to_encode, msg, webvtt)
×
137
    add_encoding_log(video_to_encode.id, msg)
×
138

139

140
def save_vtt_and_notify(video_to_encode, msg, webvtt):
1✔
141
    """Call save vtt file function and notify by mail at the end."""
142
    msg += saveVTT(video_to_encode, webvtt)
×
143
    change_encoding_step(video_to_encode.id, 0, "done")
×
144
    # envois mail fin transcription
145
    if EMAIL_ON_TRANSCRIPTING_COMPLETION:
×
146
        send_email_transcript(video_to_encode)
×
147
    add_encoding_log(video_to_encode.id, msg)
×
148

149

150
def saveVTT(video, webvtt):
1✔
151
    """Save webvtt file with the video."""
152
    msg = "\nSAVE TRANSCRIPT WEBVTT : %s" % time.ctime()
×
153
    lang = video.transcript
×
154
    temp_vtt_file = NamedTemporaryFile(suffix=".vtt")
×
155
    webvtt.save(temp_vtt_file.name)
×
156
    if webvtt.captions:
×
157
        improveCaptionsAccessibility(webvtt)
×
158
        msg += "\nstore vtt file in bdd with CustomFileModel model file field"
×
159
        if __FILEPICKER__:
×
160
            videodir, created = UserFolder.objects.get_or_create(
×
161
                name="%s" % video.slug, owner=video.owner
162
            )
163
            """
164
            previousSubtitleFile = CustomFileModel.objects.filter(
165
                name__startswith="subtitle_%s" % lang,
166
                folder=videodir,
167
                created_by=video.owner
168
            )
169
            """
170
            # for subt in previousSubtitleFile:
171
            #     subt.delete()
172
            subtitleFile, created = CustomFileModel.objects.get_or_create(
×
173
                name="subtitle_%s_%s" % (lang, time.strftime("%Y%m%d-%H%M%S")),
174
                folder=videodir,
175
                created_by=video.owner,
176
            )
177
            if subtitleFile.file and os.path.isfile(subtitleFile.file.path):
×
178
                os.remove(subtitleFile.file.path)
×
179
        else:
180
            subtitleFile, created = CustomFileModel.objects.get_or_create()
×
181

182
        subtitleFile.file.save(
×
183
            "subtitle_%s_%s.vtt" % (lang, time.strftime("%Y%m%d-%H%M%S")),
184
            File(temp_vtt_file),
185
        )
186
        msg += "\nstore vtt file in bdd with Track model src field"
×
187

188
        subtitleVtt, created = Track.objects.get_or_create(video=video, lang=lang)
×
189
        subtitleVtt.src = subtitleFile
×
190
        subtitleVtt.lang = lang
×
191
        subtitleVtt.save()
×
192
    else:
193
        msg += "\nERROR SUBTITLES Output size is 0"
×
194
    return msg
×
195

196

197
def improveCaptionsAccessibility(webvtt):
1✔
198
    """
199
    Parse the vtt file in argument to render the caption conform to accessibility.
200
    - see `https://github.com/knarf18/Bonnes-pratiques-du-sous-titrage/blob/master/Liste%20de%20bonnes%20pratiques.md` # noqa: E501
201
    - 40 car maximum per ligne (CPL)
202
    - 2 lines max by caption
203

204
    Args:
205
        webvtt (:class:`webvtt.WebVTT`): the webvtt file content
206

207
    """
208
    new_captions = []
×
209
    for caption in webvtt.captions:
×
210
        sent = split_string(caption.text, 40, sep=" ")
×
211
        if len(sent) > 2:
×
212
            num_captions = int(len(sent) / 2)
×
213
            if len(sent) % 2:
×
214
                num_captions += 1
×
215
            dur = caption.end_in_seconds - caption.start_in_seconds
×
216
            for x in range(num_captions):
×
217
                new_cap = Caption()
×
218
                new_cap.start = sec_to_timestamp(
×
219
                    caption.start_in_seconds + x * dur / num_captions
220
                )
221
                new_cap.end = sec_to_timestamp(
×
222
                    caption.start_in_seconds + (x + 1) * dur / num_captions
223
                )
224
                new_cap.text = get_cap_text(sent, x)
×
225
                new_captions.append(new_cap)
×
226
        else:
227
            new_cap = Caption()
×
228
            new_cap.start = caption.start
×
229
            new_cap.end = caption.end
×
230
            new_cap.text = "\n".join(sent)
×
231
            new_captions.append(new_cap)
×
232
    # remove all old captions
233
    while len(webvtt.captions) > 0:
×
234
        del webvtt.captions[0]
×
235
    # add the new one
236
    for cap in new_captions:
×
237
        webvtt.captions.append(cap)
×
238
    webvtt.save()
×
239

240

241
def get_cap_text(sent, x):
1✔
242
    """
243
    Get the text in the sent array at the position gived in arg.
244

245
    Args:
246
        sent (list): The list of text
247
        x (int): The position to extract
248

249
    Returns:
250
        str: The extracted text
251
    """
252
    new_cap_text = sent[x * 2]
×
253
    try:
×
254
        new_cap_text += "\n" + sent[x * 2 + 1]
×
255
    except IndexError:
×
256
        pass
×
257
    return new_cap_text
×
258

259

260
def pad(line, limit):
1✔
261
    """
262
    Add some space at the end of line to specified limit.
263

264
    Args:
265
        line (str): A line of text
266
        limit (int): The size of line
267

268
    Returns:
269
        str: the line with space at the end
270
    """
271
    return line + " " * (limit - len(line))
×
272

273

274
def split_string(text, limit, sep=" "):
1✔
275
    """
276
    Split text by word for specified limit.
277

278
    Args:
279
        text (str): the text of the caption
280
        limit (int): size of line
281
        sep (str): default " "
282

283
    Returns:
284
        array: list of words in the text
285
    """
286
    words = text.split()
×
287
    if max(map(len, words)) > limit:
×
288
        raise ValueError("limit is too small")
×
289
    res = []
×
290
    part = words[0]
×
291
    others = words[1:]
×
292
    for word in others:
×
293
        if len(sep) + len(word) > limit - len(part):
×
294
            res.append(part)
×
295
            part = word
×
296
        else:
297
            part += sep + word
×
298
    if part:
×
299
        res.append(part)
×
300
    # add space to the end of line
301
    result = [pad(line, limit) for line in res]
×
302
    return result
×
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