Skip to content

import_files

supported_file_types module-attribute

supported_file_types = {
    "Aegisub Project (*.ass)": import_ass,
    "AvsP Session (*.ses)": import_ses,
    "CUE Sheet (*.cue)": import_cue,
    "DGIndex Project (*.dgi)": import_dgi,
    "IfoEdit Celltimes (*.txt)": import_celltimes,
    "L-SMASH Works Index (*.lwi)": import_lwi,
    "Matroska Timestamps v1 (*.txt)": import_matroska_timestamps_v1,
    "Matroska Timestamps v2 (*.txt)": import_matroska_timestamps_v2,
    "Matroska Timestamps v3 (*.txt)": import_matroska_timestamps_v3,
    "Matroska XML Chapters (*.xml)": import_matroska_xml_chapters,
    "OGM Chapters (*.txt)": import_ogm_chapters,
    "TFM Log (*.txt)": import_tfm,
    "VSEdit Bookmarks (*.bookmarks)": import_vsedit,
    "Wobbly File (*.wob)": import_wobbly,
    "Wobbly Sections (*.txt)": import_wobbly_sections,
    "x264/x265 2 Pass Log (*.log)": import_x264_2pass_log,
    "x264/x265 QP File (*.qp *.txt)": import_qp,
    "XviD Log (*.txt)": import_xvid,
    "Generic Mappings (*.txt)": import_generic,
}

TFMFrame

TFMFrame(init_value: Number | Frame | Time | None = 0)

Bases: Frame

Source code
22
23
24
25
26
27
28
29
30
31
32
def __init__(self, init_value: Number | Frame | Time | None = 0) -> None:
    if isinstance(init_value, float):
        init_value = int(init_value)
    if isinstance(init_value, int):
        self.value = init_value
    elif isinstance(init_value, Frame):
        self.value = init_value.value
    elif isinstance(init_value, Time):
        self.value = main_window().current_output.to_frame(init_value).value
    else:
        raise TypeError

mic instance-attribute

mic: int | None

storable_attrs class-attribute

storable_attrs: tuple[str, ...] = ()

value instance-attribute

value = init_value

set_qobject_names

set_qobject_names() -> None
Source code
279
280
281
282
283
284
285
286
287
288
289
290
291
292
def set_qobject_names(self) -> None:
    if not hasattr(self, '__slots__'):
        return

    slots = list(self.__slots__)

    if isinstance(self, AbstractToolbar) and 'main' in slots:
        slots.remove('main')

    for attr_name in slots:
        attr = getattr(self, attr_name)
        if not isinstance(attr, QObject):
            continue
        attr.setObjectName(type(self).__name__ + '.' + attr_name)

import_ass

import_ass(path: Path, scening_list: SceningList) -> int

Imports events as scenes. Event content is ignored.

Source code
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def import_ass(path: Path, scening_list: SceningList) -> int:
    """
    Imports events as scenes.
    Event content is ignored.
    """

    out_of_range_count = 0

    try:
        from pysubs2 import load as pysubs2_load  # type: ignore[import-not-found]
    except ModuleNotFoundError:
        raise RuntimeError(
            'vspreview: Can\'t import scenes from ass file, you\'re missing the `pysubs2` package!'
        )

    subs = pysubs2_load(str(path))

    for line in subs:
        t_start = Time(milliseconds=line.start)
        t_end = Time(milliseconds=line.end)

        try:
            scening_list.add(Frame(t_start), Frame(t_end))
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count

import_celltimes

import_celltimes(path: Path, scening_list: SceningList) -> int

Imports cell times as single-frame scenes.

Source code
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def import_celltimes(path: Path, scening_list: SceningList) -> int:
    """
    Imports cell times as single-frame scenes.
    """

    out_of_range_count = 0

    for line in path.read_text('utf8').splitlines():
        try:
            scening_list.add(Frame(int(line)))
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count

import_cue

import_cue(path: Path, scening_list: SceningList) -> int

Imports tracks as scenes. Uses TITLE for scene label.

Source code
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def import_cue(path: Path, scening_list: SceningList) -> int:
    """
    Imports tracks as scenes.
    Uses TITLE for scene label.
    """

    out_of_range_count = 0

    try:
        from cueparser import CueSheet  # type: ignore[import-not-found]
    except ModuleNotFoundError:
        raise RuntimeError(
            'vspreview: Can\'t import scenes from cue file, you\'re missing the `cueparser` package!'
        )

    def offset_to_time(offset: str) -> Time | None:
        pattern = re.compile(r'(\d{1,2}):(\d{1,2}):(\d{1,2})')
        match = pattern.match(offset)

        if match is None:
            return None

        return Time(minutes=int(match[1]), seconds=int(match[2]), milliseconds=int(match[3]) / 75 * 1000)

    cue_sheet = CueSheet()
    cue_sheet.setOutputFormat('')
    cue_sheet.setData(path.read_text('utf8'))
    cue_sheet.parse()

    for track in cue_sheet.tracks:
        if track.offset is None:
            continue

        offset = offset_to_time(track.offset)

        if offset is None:
            logging.warning(f"Scening import: INDEX timestamp '{track.offset}' format isn't supported.")
            continue

        start = Frame(offset)
        end = None

        if track.duration is not None:
            end = Frame(offset + Time(track.duration))

        label = ''

        if track.title is not None:
            label = track.title

        try:
            scening_list.add(start, end, label)
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count

import_dgi

import_dgi(path: Path, scening_list: SceningList) -> int

Imports IDR frames as single-frame scenes.

Source code
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def import_dgi(path: Path, scening_list: SceningList) -> int:
    """
    Imports IDR frames as single-frame scenes.
    """

    out_of_range_count = 0

    pattern = re.compile(r'IDR\s\d+\n(\d+):FRM', re.RegexFlag.MULTILINE)

    for match in pattern.findall(path.read_text('utf8')):
        try:
            scening_list.add(Frame(match))
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count

import_generic

import_generic(path: Path, scening_list: SceningList) -> int

Import generic (rfs style) frame mappings: {start end}.

Source code
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
def import_generic(path: Path, scening_list: SceningList) -> int:
    """
    Import generic (rfs style) frame mappings: {start end}.
    """

    out_of_range_count = 0

    for line in path.read_text('utf8').splitlines():
        try:
            fnumbers = [int(n) for n in line.split()]
            scening_list.add(Frame(fnumbers[0]), Frame(fnumbers[1]))
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count

import_lwi

import_lwi(path: Path, scening_list: SceningList) -> int

Imports Key=1 frames as single-frame scenes. Ignores everything besides Index=0 video stream.

Source code
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
def import_lwi(path: Path, scening_list: SceningList) -> int:
    """
    Imports Key=1 frames as single-frame scenes.
    Ignores everything besides Index=0 video stream.
    """

    out_of_range_count = 0

    AV_CODEC_ID_FIRST_AUDIO = 0x10000
    STREAM_INDEX = 0
    IS_KEY = 1

    pattern = re.compile(r'Index={}.*?Codec=(\d+).*?\n.*?Key=(\d)'.format(
        STREAM_INDEX
    ))

    frame = Frame(0)

    for match in pattern.finditer(path.read_text('utf8'), re.RegexFlag.MULTILINE):
        if int(match[1]) >= AV_CODEC_ID_FIRST_AUDIO:
            frame += Frame(1)
            continue

        if not int(match[2]) == IS_KEY:
            frame += Frame(1)
            continue

        try:
            scening_list.add(deepcopy(frame))
        except ValueError:
            out_of_range_count += 1

        frame += Frame(1)

    return out_of_range_count

import_matroska_timestamps_v1

import_matroska_timestamps_v1(path: Path, scening_list: SceningList) -> int

Imports listed scenes. Uses FPS for scene label.

Source code
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
def import_matroska_timestamps_v1(path: Path, scening_list: SceningList) -> int:
    """
    Imports listed scenes.
    Uses FPS for scene label.
    """

    out_of_range_count = 0

    pattern = re.compile(r'(\d+),(\d+),(\d+(?:\.\d+)?)')

    for match in pattern.finditer(path.read_text('utf8')):
        try:
            scening_list.add(
                Frame(int(match[1])), Frame(int(match[2])), '{:.3f} fps'.format(float(match[3]))
            )
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count

import_matroska_timestamps_v2

import_matroska_timestamps_v2(path: Path, scening_list: SceningList) -> int

Imports intervals of constant FPS as scenes. Uses FPS for scene label.

Source code
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
def import_matroska_timestamps_v2(path: Path, scening_list: SceningList) -> int:
    """
    Imports intervals of constant FPS as scenes.
    Uses FPS for scene label.
    """

    out_of_range_count = 0

    timestamps = list[Time]()

    for line in path.read_text('utf8').splitlines():
        try:
            timestamps.append(Time(milliseconds=float(line)))
        except ValueError:
            continue

    if len(timestamps) < 2:
        logging.warning(
            "Scening import: timestamps file contains less than 2 timestamps, so there's nothing to import."
        )
        return out_of_range_count

    deltas = [
        timestamps[i] - timestamps[i - 1]
        for i in range(1, len(timestamps))
    ]

    scene_delta = deltas[0]
    scene_start = Frame(0)
    scene_end: Frame | None = None

    for i in range(1, len(deltas)):
        if abs(round(float(deltas[i] - scene_delta), 6)) <= 0.000_001:
            continue

        # TODO: investigate, why offset by -1 is necessary here
        scene_end = Frame(i - 1)

        try:
            scening_list.add(scene_start, scene_end, '{:.3f} fps'.format(1 / float(scene_delta)))
        except ValueError:
            out_of_range_count += 1

        scene_start = Frame(i)
        scene_end = None
        scene_delta = deltas[i]

    if scene_end is None:
        try:
            scening_list.add(
                scene_start, Frame(len(timestamps) - 1),
                '{:.3f} fps'.format(1 / float(scene_delta))
            )
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count

import_matroska_timestamps_v3

import_matroska_timestamps_v3(path: Path, scening_list: SceningList) -> int

Imports listed scenes, ignoring gaps. Uses FPS for scene label.

Source code
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
def import_matroska_timestamps_v3(path: Path, scening_list: SceningList) -> int:
    """
    Imports listed scenes, ignoring gaps.
    Uses FPS for scene label.
    """

    out_of_range_count = 0

    pattern = re.compile(
        r'^((?:\d+(?:\.\d+)?)|gap)(?:,\s?(\d+(?:\.\d+)?))?',
        re.RegexFlag.MULTILINE
    )

    assume_pattern = re.compile(r'assume (\d+(?:\.\d+))')

    if len(mmatch := assume_pattern.findall(path.read_text('utf8'))) > 0:
        default_fps = float(mmatch[0])
    else:
        logging.warning('Scening import: "assume" entry not found.')
        return out_of_range_count

    pos = Time()

    for match in pattern.finditer(path.read_text('utf8')):
        if match[1] == 'gap':
            pos += Time(seconds=float(match[2]))
            continue

        interval = Time(seconds=float(match[1]))
        fps = float(match[2]) if (match.lastindex or 0) >= 2 else default_fps

        try:
            scening_list.add(Frame(pos), Frame(pos + interval), '{:.3f} fps'.format(fps))
        except ValueError:
            out_of_range_count += 1

        pos += interval

    return out_of_range_count

import_matroska_xml_chapters

import_matroska_xml_chapters(path: Path, scening_list: SceningList) -> int

Imports chapters as scenes. Preserve end time and text if they're present.

Source code
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
def import_matroska_xml_chapters(path: Path, scening_list: SceningList) -> int:
    """
    Imports chapters as scenes.
    Preserve end time and text if they're present.
    """

    from xml.etree import ElementTree
    out_of_range_count = 0

    timestamp_pattern = re.compile(r'(\d{2}):(\d{2}):(\d{2}(?:\.\d{3})?)')

    try:
        root = ElementTree.parse(str(path)).getroot()
    except ElementTree.ParseError as exc:
        logging.warning(f"Scening import: error occurred while parsing '{path.name}':")
        logging.warning(exc.msg)

        return out_of_range_count

    for chapter in root.iter('ChapterAtom'):
        start_element = chapter.find('ChapterTimeStart')

        if start_element is None or start_element.text is None:
            continue

        match = timestamp_pattern.match(start_element.text)

        if match is None:
            continue

        start = Frame(Time(hours=int(match[1]), minutes=int(match[2]), seconds=float(match[3])))

        end = None
        end_element = chapter.find('ChapterTimeEnd')

        if end_element is not None and end_element.text is not None:
            match = timestamp_pattern.match(end_element.text)

            if match is not None:
                end = Frame(Time(hours=int(match[1]), minutes=int(match[2]), seconds=float(match[3])))

        label = ''
        label_element = chapter.find('ChapterDisplay/ChapterString')

        if label_element is not None and label_element.text is not None:
            label = label_element.text

        try:
            scening_list.add(start, end, label)
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count

import_ogm_chapters

import_ogm_chapters(path: Path, scening_list: SceningList) -> int

Imports chapters as single-frame scenes. Uses NAME for scene label.

Source code
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def import_ogm_chapters(path: Path, scening_list: SceningList) -> int:
    """
    Imports chapters as single-frame scenes.
    Uses NAME for scene label.
    """

    out_of_range_count = 0

    pattern = re.compile(
        r'(CHAPTER\d+)=(\d+):(\d+):(\d+(?:\.\d+)?)\n\1NAME=(.*)',
        re.RegexFlag.MULTILINE
    )

    for match in pattern.finditer(path.read_text('utf8')):
        time = Time(hours=int(match[2]), minutes=int(match[3]), seconds=float(match[4]))

        try:
            scening_list.add(Frame(time), label=match[5])
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count

import_qp

import_qp(path: Path, scening_list: SceningList) -> int

Imports I- and K-frames as single-frame scenes.

Source code
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
def import_qp(path: Path, scening_list: SceningList) -> int:
    """
    Imports I- and K-frames as single-frame scenes.
    """

    out_of_range_count = 0

    pattern = re.compile(r'(\d+)\sI|K')

    for match in pattern.findall(path.read_text('utf8')):
        try:
            scening_list.add(Frame(int(match)))
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count

import_ses

import_ses(path: Path, scening_list: SceningList) -> int

Imports bookmarks as single-frame scenes.

Source code
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
def import_ses(path: Path, scening_list: SceningList) -> int:
    """
    Imports bookmarks as single-frame scenes.
    """

    out_of_range_count = 0

    import pickle

    with path.open('rb') as f:
        try:
            session = pickle.load(f)
        except pickle.UnpicklingError:
            logging.warning('Scening import: failed to load .ses file.')
            return out_of_range_count

    if 'bookmarks' not in session:
        return out_of_range_count

    for bookmark in session['bookmarks']:
        try:
            scening_list.add(Frame(bookmark[0]))
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count

import_tfm

import_tfm(path: Path, scening_list: SceningList) -> int

Imports TFM's 'OVR HELP INFORMATION'. Single combed frames are put into single-frame scenes. Frame groups are put into regular scenes. Combed probability is used for label.

Source code
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
def import_tfm(path: Path, scening_list: SceningList) -> int:
    """
    Imports TFM's 'OVR HELP INFORMATION'.
    Single combed frames are put into single-frame scenes.
    Frame groups are put into regular scenes.
    Combed probability is used for label.
    """

    out_of_range_count = 0

    tfm_frame_pattern = re.compile(r'(\d+)\s\((\d+)\)')
    tfm_group_pattern = re.compile(r'(\d+),(\d+)\s\((\d+(?:\.\d+)%)\)')

    log = path.read_text('utf8')

    start_pos = log.find('OVR HELP INFORMATION')

    if start_pos == -1:
        logging.warning("Scening import: TFM log doesn't contain OVR Help Information.")
        return out_of_range_count

    log = log[start_pos:]

    tfm_frames = set[TFMFrame]()

    for match in tfm_frame_pattern.finditer(log):
        tfm_frame = TFMFrame(int(match[1]))
        tfm_frame.mic = int(match[2])
        tfm_frames.add(tfm_frame)

    for match in tfm_group_pattern.finditer(log):
        try:
            scene = scening_list.add(Frame(int(match[1])), Frame(int(match[2])), f'{match[3]} combed')
        except ValueError:
            out_of_range_count += 1
            continue

        tfm_frames -= set(range(int(scene.start), int(scene.end) + 1))

    for tfm_frame in tfm_frames:
        try:
            scening_list.add(tfm_frame, label=str(tfm_frame.mic))
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count

import_vsedit

import_vsedit(path: Path, scening_list: SceningList) -> int

Imports bookmarks as single-frame scenes.

Source code
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
def import_vsedit(path: Path, scening_list: SceningList) -> int:
    """
    Imports bookmarks as single-frame scenes.
    """

    out_of_range_count = 0

    frames = []

    for bookmark in path.read_text('utf8').split(', '):
        try:
            frames.append(int(bookmark))
        except ValueError:
            out_of_range_count += 1

    ranges = list[list[int]]()
    prev_x: int = 0

    for x in frames:
        if not ranges:
            ranges.append([x])
        elif x - prev_x == 1:
            ranges[-1].append(x)
        else:
            ranges.append([x])

        prev_x = int(x)

    for rang in ranges:
        scening_list.add(
            Frame(rang[0]),
            Frame(rang[-1]) if len(rang) > 1 else None
        )

    return out_of_range_count

import_wobbly

import_wobbly(path: Path, scening_list: SceningList) -> int

Imports sections from a Wobbly file as scenes.

End frames of each scene are obtained from the next section's start frame. The final scene's end frame is the trim end. Section preset is used as scene label.

Source code
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
def import_wobbly(path: Path, scening_list: SceningList) -> int:
    """
    Imports sections from a Wobbly file as scenes.

    End frames of each scene are obtained from the next section's start frame.
    The final scene's end frame is the trim end.
    Section preset is used as scene label.
    """

    out_of_range_count = 0

    try:
        wobbly_data = dict(json.loads(path.read_text('utf8')))
        logging.debug(f'Successfully loaded wobbly file: {path.name}')
    except json.JSONDecodeError as e:
        err_msg = f'Scening import: Failed to decode the wobbly file, \'{path.name}\''
        logging.warning(f'{err_msg}:\n{str(e)}')

        raise RuntimeError(err_msg)

    if not (sections := wobbly_data.get('sections', [])):
        logging.warning('Scening import: No sections found in wobbly file')

        return 0

    if (missing_starts := [i for i, s in enumerate(sections) if not isinstance(s, int) and 'start' not in s]):
        logging.warning(f'Scening import: Sections missing start frames at indices: {missing_starts}')

        raise RuntimeError(f'Scening import: Sections missing start frames at indices: {missing_starts}')

    start_frames = [dict(s).get('start', 0) for s in sections]
    logging.debug(f'Found {len(start_frames)} section start frames')

    presets = [dict(s).get('preset', []) for s in sections]
    logging.debug(f'Found {len(presets)} section presets')

    trim = wobbly_data.get('trim', [[0, 0]])[0]
    logging.debug(f'Trim value: {trim}')

    end_frames = start_frames[1:] + [trim[1]]
    logging.debug(f'Generated {len(end_frames)} section end frames')

    if not (decimations := wobbly_data.get('decimated frames', {})):
        logging.debug('No decimation data found, using raw frame numbers')

        for start, end, preset in zip(start_frames, end_frames, presets):
            try:
                label = str(preset) if preset else ''
                scening_list.add(Frame(start), Frame(end), label)
                logging.debug(f'Added scene: {start} -> {end} {"with label: " + label if label else ""}')
            except ValueError:
                out_of_range_count += 1
                logging.debug(f'Frame out of range: {start} -> {end}')

        return out_of_range_count

    sorted_decimations = sorted(decimations)
    logging.debug(f'Found {len(sorted_decimations)} decimated frames')

    for start, end, preset in zip(start_frames, end_frames, presets):
        try:
            adjusted_start = start - bisect_left(sorted_decimations, start)
            adjusted_end = end - bisect_left(sorted_decimations, end)
            label = str(preset) if preset else ''
            scening_list.add(Frame(adjusted_start), Frame(adjusted_end), label)
            logging.debug(
                f'Added decimation-adjusted scene: {adjusted_start} -> {adjusted_end} '
                f'{"with label: " + label if label else ""}'
            )
        except ValueError:
            out_of_range_count += 1
            logging.debug(f'Frame out of range: {start} -> {end}')

    return out_of_range_count

import_wobbly_sections

import_wobbly_sections(path: Path, scening_list: SceningList) -> int

Imports section start frames from a Wobbly sections file as single-frame scenes.

Source code
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
def import_wobbly_sections(path: Path, scening_list: SceningList) -> int:
    """
    Imports section start frames from a Wobbly sections file as single-frame scenes.
    """

    try:
        sections = [int(line) for line in path.read_text('utf8').splitlines() if line.strip()]
        logging.debug(f'Successfully loaded wobbly sections file: {path.name}')
    except ValueError as e:
        err_msg = f'Scening import: Failed to parse the wobbly sections file, \'{path.name}\''
        logging.warning(f'{err_msg}:\n{str(e)}')

        raise RuntimeError(err_msg)

    if not sections:
        logging.warning('Scening import: No sections found in wobbly sections file')

        return 0

    out_of_range_count = 0

    for frame in sections:
        try:
            scening_list.add(Frame(frame))
            logging.debug(f'Added section frame: {frame}')
        except ValueError:
            out_of_range_count += 1
            logging.debug(f'Frame out of range: {frame}')

    return out_of_range_count

import_x264_2pass_log

import_x264_2pass_log(path: Path, scening_list: SceningList) -> int

Imports I- and K-frames as single-frame scenes.

Source code
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
def import_x264_2pass_log(path: Path, scening_list: SceningList) -> int:
    """
    Imports I- and K-frames as single-frame scenes.
    """

    out_of_range_count = 0

    pattern = re.compile(r'in:(\d+).*type:I|K')

    for match in pattern.findall(path.read_text('utf8')):
        try:
            scening_list.add(Frame(int(match)))
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count

import_xvid

import_xvid(path: Path, scening_list: SceningList) -> int

Imports I-frames as single-frame scenes.

Source code
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
def import_xvid(path: Path, scening_list: SceningList) -> int:
    """
    Imports I-frames as single-frame scenes.
    """

    out_of_range_count = 0

    for i, line in enumerate(path.read_text('utf8').splitlines()):
        if not line.startswith('i'):
            continue
        try:
            scening_list.add(Frame(i - 3))
        except ValueError:
            out_of_range_count += 1

    return out_of_range_count