Skip to content

import_files

Attributes:

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,
    "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

Methods:

Attributes:

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 lines as scenes. Text is ignored.

Source code
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def import_ass(path: Path, scening_list: SceningList) -> int:
    """
    Imports lines as scenes.
    Text 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
42
43
44
45
46
47
48
49
50
51
52
53
54
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
 57
 58
 59
 60
 61
 62
 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
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
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
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
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
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
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
420
421
422
423
424
425
426
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
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
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_x264_2pass_log

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

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

Source code
463
464
465
466
467
468
469
470
471
472
473
474
475
476
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
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
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