Skip to content

window

Classes:

CentralSplitter

CentralSplitter(main_window: MainWindow, orientation: Orientation)

Bases: QSplitter

Methods:

Attributes:

Source code
61
62
63
64
65
66
67
68
def __init__(self, main_window: MainWindow, orientation: QtCore.Qt.Orientation) -> None:
    super().__init__(orientation)

    self.main_window = main_window

    self.splitterMoved.connect(self.on_splitter_moved)

    self.previous_position = 0

current_position property

current_position: int

main_window instance-attribute

main_window = main_window

previous_position instance-attribute

previous_position = 0

on_splitter_moved

on_splitter_moved() -> None
Source code
74
75
76
77
78
def on_splitter_moved(self) -> None:
    if self.previous_position == 0 and self.current_position:
        self.main_window.plugins.update()

    self.previous_position = self.current_position

MainWindow

MainWindow(
    config_dir: SPath, no_exit: bool, reload_enabled: bool, force_storage: bool
)

Bases: AbstractQItem, QMainWindow, QAbstractYAMLObjectSingleton

Methods:

Attributes:

Source code
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
158
159
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
204
205
206
207
208
209
210
def __init__(self, config_dir: SPath, no_exit: bool, reload_enabled: bool, force_storage: bool) -> None:
    from ..toolbars import MainToolbar

    super().__init__()

    self.move_legacy_vspdir()
    self.move_legacy_global_storage()

    self.no_exit = no_exit
    self.reload_enabled = reload_enabled
    self.force_storage = force_storage

    self.resolve_plugins = set[FileResolverPlugin]()

    self.settings = MainSettings(MainToolbar)

    self.current_config_dir = PackageStorage(config_dir).folder
    self.global_plugins_dir.mkdir(parents=True, exist_ok=True)

    self.app = cast(QApplication, QApplication.instance())
    assert self.app

    self.last_reload_time = time()

    self.bound_graphics_views = dict[GraphicsView, set[GraphicsView]]()

    self.setWindowTitle('VSPreview')

    desktop_size = self.app.primaryScreen().size()

    self.move(int(desktop_size.width() * 0.15), int(desktop_size.height() * 0.075))
    self.setup_ui()
    self.storage_not_found = False
    self.timecodes = dict[
        int, tuple[str | SPath | dict[
            tuple[int | None, int | None], float | tuple[int, int] | Fraction
        ] | list[Fraction], int | None]
    ]()
    self.temporary_scenes = list[SceningList]()
    self.norm_timecodes = dict[int, list[float]]()

    self.user_output_info = {
        vs.VideoNode: dict[int, dict[str, Any]](),
        vs.AudioNode: dict[int, dict[str, Any]](),
        vs.RawNode: dict[int, dict[str, Any]]()
    }

    # global
    self.clipboard = self.app.clipboard()
    self.external_args = list[tuple[str, str]]()
    self.script_path = SPath()
    self.script_exec_failed = False
    self.current_storage_path = SPath()

    # timeline
    self.timeline.clicked.connect(self.on_timeline_clicked)

    # display profile
    self.display_profile: QColorSpace | None = None
    self.current_screen = self.app.primaryScreen()

    # init toolbars and outputs
    self.app_settings = SettingsDialog(self)

    Toolbars(self)

    for toolbar in self.toolbars:
        self.main_layout.addWidget(toolbar)
        self.toolbars.main.layout().addWidget(toolbar.toggle_button)

    Plugins(self)

    self.toolbars.main.layout().addWidget(PushButton("Plugins", clicked=self.pop_out_plugins))

    self.app_settings.tab_widget.setUsesScrollButtons(False)
    self.app_settings.setMinimumWidth(
        int((len(self.toolbars) + 1) * 1.2 * self.app_settings.tab_widget.geometry().width() / 2)
    )

    self.shortcuts = ShortCutsSettings(self)

    self.set_qobject_names()
    self.setObjectName('MainWindow')

    self.env: vpy.Script | None = None

BREAKING_CHANGES_VERSIONS class-attribute instance-attribute

BREAKING_CHANGES_VERSIONS = list[str](['3.0', '3.1'])

EVENT_POLICY class-attribute instance-attribute

EVENT_POLICY = QSizePolicy(Expanding, Expanding)

VSP_DIR_NAME class-attribute instance-attribute

VSP_DIR_NAME = 'vspreview'

VSP_GLOBAL_DIR_NAME class-attribute instance-attribute

VSP_GLOBAL_DIR_NAME = SPath(
    expandvars("%APPDATA%") if platform == "win32" else expanduser("~/.config")
)

VSP_VERSION class-attribute instance-attribute

VSP_VERSION = 3.2

app instance-attribute

app = cast(QApplication, instance())

app_settings instance-attribute

app_settings: SettingsDialog = SettingsDialog(self)

arValuesChanged class-attribute instance-attribute

arValuesChanged = pyqtSignal(ArInfo)

autosave_timer instance-attribute

autosave_timer: Timer

bound_graphics_views instance-attribute

bound_graphics_views = dict[GraphicsView, set[GraphicsView]]()

clipboard instance-attribute

clipboard = clipboard()

cropValuesChanged class-attribute instance-attribute

cropValuesChanged = pyqtSignal(CroppingInfo)

current_config_dir instance-attribute

current_config_dir = folder

current_output property

current_output: VideoOutput

current_scene property

current_scene: GraphicsImageItem

current_screen instance-attribute

current_screen = primaryScreen()

current_storage_path instance-attribute

current_storage_path = SPath()

display_profile instance-attribute

display_profile: QColorSpace | None = None

display_scale property

display_scale: float

env instance-attribute

env: Script | None = None

external_args instance-attribute

external_args = list[tuple[str, str]]()

force_storage instance-attribute

force_storage = force_storage

global_config_dir class-attribute instance-attribute

global_config_dir = VSP_GLOBAL_DIR_NAME / VSP_DIR_NAME

global_plugins_dir class-attribute instance-attribute

global_plugins_dir = global_config_dir / 'plugins'

global_storage_path class-attribute instance-attribute

global_storage_path = global_config_dir / 'global.yml'

graphics_views property

graphics_views: list[GraphicsView]

last_reload_time instance-attribute

last_reload_time = time()

no_exit instance-attribute

no_exit: bool = no_exit

norm_timecodes instance-attribute

norm_timecodes = dict[int, list[float]]()

outputs property

outputs: VideoOutputs | None

plugins instance-attribute

plugins: Plugins

reload_after_signal class-attribute instance-attribute

reload_after_signal = pyqtSignal()

reload_before_signal class-attribute instance-attribute

reload_before_signal = pyqtSignal()

reload_enabled instance-attribute

reload_enabled: bool = reload_enabled

reload_signal class-attribute instance-attribute

reload_signal = pyqtSignal()

reload_stylesheet_signal class-attribute instance-attribute

reload_stylesheet_signal = pyqtSignal()

resolve_plugins instance-attribute

resolve_plugins = set[FileResolverPlugin]()

script_exec_failed instance-attribute

script_exec_failed = False

script_path instance-attribute

script_path = SPath()

settings instance-attribute

shortcuts instance-attribute

storable_attrs class-attribute instance-attribute

storable_attrs = ('settings', 'toolbars', 'plugins', 'shortcuts')

storage_not_found instance-attribute

storage_not_found = False

temporary_scenes instance-attribute

temporary_scenes = list[SceningList]()

timecodes instance-attribute

timecodes = dict[
    int,
    tuple[
        str
        | SPath
        | dict[
            tuple[int | None, int | None], float | tuple[int, int] | Fraction
        ]
        | list[Fraction],
        int | None,
    ],
]()

toolbars instance-attribute

toolbars: Toolbars

user_output_info instance-attribute

user_output_info = {
    VideoNode: dict[int, dict[str, Any]](),
    AudioNode: dict[int, dict[str, Any]](),
    RawNode: dict[int, dict[str, Any]](),
}

window_settings class-attribute instance-attribute

window_settings = WindowSettings()

apply_stylesheet

apply_stylesheet() -> None
Source code
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
def apply_stylesheet(self) -> None:
    try:
        from qdarkstyle import DarkPalette, LightPalette, _load_stylesheet  # type: ignore[import]
    except ImportError:
        self.settings.dark_theme_enabled = False
    else:
        palette = DarkPalette if self.settings.dark_theme_enabled else LightPalette
        if self.settings.dark_theme_enabled:
            apply_plotting_style()

        stylesheet = _load_stylesheet('pyqt6', palette)
        stylesheet += ' QGraphicsView { border: 0px; padding: 0px; }'
        stylesheet += ' QLineEdit[conflictShortcut="true"] { border: 1px solid red; }'

        self.app.setStyleSheet(stylesheet)

    if sys.platform == 'win32':
        self.app.setStyle("windowsvista")

    self.ensurePolished()
    self.reload_stylesheet_signal.emit()
    self.repaint()

auto_fit_keyswitch

auto_fit_keyswitch() -> None
Source code
258
259
260
261
262
def auto_fit_keyswitch(self) -> None:
    for view in self.graphics_views:
        if view.underMouse():
            view.auto_fit_button.click()
            break

clean_core_references

clean_core_references() -> None
Source code
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
def clean_core_references(self) -> None:
    from vstools.utils.vs_proxy import clear_cache

    for graphics_view in self.graphics_views:
        graphics_view.graphics_scene.clear()

    self.timecodes.clear()
    self.norm_timecodes.clear()

    self.toolbars.pipette._curr_frame_cache.clear()
    self.toolbars.pipette._curr_alphaframe_cache.clear()
    self.toolbars.pipette.outputs.clear()

    for v in self.user_output_info.values():
        for k in v.values():
            k.clear()
        v.clear()

    try:
        with self.env:
            clear_cache()
    except Exception:
        ...

    vs.clear_outputs()

    if self.outputs:
        self.outputs.clear()

    self.gc_collect()

clear_monkey_runpy

clear_monkey_runpy() -> None
Source code
760
761
762
763
764
765
766
767
768
769
770
771
772
def clear_monkey_runpy(self) -> None:
    if self.env and '_monkey_runpy' in self.env.module.__dict__:
        key = self.env.module.__dict__['_monkey_runpy']

        if key in _monkey_runpy_dicts:
            _monkey_runpy_dicts[key].clear()
            _monkey_runpy_dicts.pop(key, None)
        elif _monkey_runpy_dicts:
            for env in _monkey_runpy_dicts.values():
                env.clear()
            _monkey_runpy_dicts.clear()

    self.gc_collect()

closeEvent

closeEvent(event: QCloseEvent) -> None
Source code
980
981
982
983
984
985
986
987
def closeEvent(self, event: QCloseEvent) -> None:
    if self.settings.autosave_control.value() != Time(seconds=0):
        self.dump_storage_async()

    self.reload_signal.emit()

    for file_resolve_plugin in self.resolve_plugins:
        file_resolve_plugin.cleanup()

dump_storage

dump_storage() -> None
Source code
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 dump_storage(self) -> None:
    from itertools import count

    if self.script_exec_failed:
        return

    self.current_config_dir.mkdir(0o777, True, True)
    self.global_config_dir.mkdir(0o777, True, True)

    backup_paths = [
        self.current_storage_path.with_suffix(f'.old{i}.yml')
        for i in range(self.settings.STORAGE_BACKUPS_COUNT, 0, -1)
    ] + [self.current_storage_path]

    for src_path, dest_path in zip(backup_paths[1:], backup_paths[:-1]):
        if src_path.exists():
            src_path.replace(dest_path)

    storage_dump = self._dump_serialize(self._serialize_data()).splitlines()

    idx = next(idx for (line, idx) in zip(storage_dump[2:], count(2)) if not line.startswith(' '))

    version = f'# Version@{self.VSP_VERSION}'

    with io.open(self.global_storage_path, 'w', encoding='utf-8') as global_file:
        global_file.writelines(
            '\n'.join([version, '# Global VSPreview storage for settings'] + storage_dump[:idx])
        )

    with io.open(self.current_storage_path, 'w', encoding='utf-8') as current_file:
        current_file.writelines(
            '\n'.join([
                version,
                f'# VSPreview local storage for script: {self.script_path}',
                f'# Global setting (storage/plugins) saved at path: {self.global_config_dir}'
            ] + storage_dump[idx:])
        )

dump_storage_async

dump_storage_async() -> None
Source code
596
597
598
599
@set_status_label('Saving storage...', 'Storage saved successfully!')
@fire_and_forget
def dump_storage_async(self) -> None:
    self.dump_storage()

event

event(event: QEvent) -> bool
Source code
966
967
968
969
970
def event(self, event: QEvent) -> bool:
    if event.type() == QEvent.Type.LayoutRequest:
        self.timeline.update()

    return super().event(event)

gc_collect

gc_collect() -> None
Source code
774
775
776
777
778
779
780
781
def gc_collect(self) -> None:
    import gc

    for i in range(3):
        gc.collect(generation=i)

    for _ in range(3):
        gc.collect()

handle_error

handle_error(e: Exception) -> None
Source code
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
def handle_error(self, e: Exception) -> None:
    import logging

    from traceback import TracebackException

    from vsengine import vpy

    if not isinstance(e, vpy.ExecutionFailed):
        e = vpy.ExecutionFailed(e)

    self.hide()
    self.apply_stylesheet()

    te = TracebackException.from_exception(e.parent_error)
    logging.error(''.join(te.format()))

    if isinstance(e.parent_error, SyntaxError) and (
        'source code string cannot contain null bytes' in str(
            e.parent_error).lower()
    ):
        logging.error(
            'If you\'re trying to open a video you first have to install '
            'the vssource package and have it working with a source plugin!'
        )

    self.script_exec_failed = True
    self.handle_script_error(
        '\n'.join([
            str(e), 'See console output for details.'
        ]), True
    )

handle_script_error

handle_script_error(message: str, script: bool = False) -> None
Source code
882
883
884
885
886
def handle_script_error(self, message: str, script: bool = False) -> None:
    self.clear_monkey_runpy()
    self.script_error_dialog.label.setText(message)
    self.script_error_dialog.setWindowTitle('Script Loading Error' if script else 'Program Error')
    self.script_error_dialog.open()

init_outputs

init_outputs() -> None
Source code
697
698
699
700
701
702
703
704
def init_outputs(self) -> None:
    if not self.outputs:
        return

    self.plugins.init_outputs()

    for graphics_view in self.graphics_views:
        graphics_view.graphics_scene.init_scenes()

load_script

load_script(
    script_path: SPath,
    external_args: list[tuple[str, str]] | None = None,
    reloading: bool = False,
    start_frame: int | None = None,
    display_name: str | None = None,
    resolve_plugin: FileResolverPlugin | None = None,
) -> None
Source code
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
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
379
380
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
420
421
422
423
424
425
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
def load_script(
    self, script_path: SPath, external_args: list[tuple[str, str]] | None = None, reloading: bool = False,
    start_frame: int | None = None, display_name: str | None = None,
    resolve_plugin: FileResolverPlugin | None = None
) -> None:
    from random import random

    self.display_name = display_name or script_path
    self.external_args = external_args or []
    self.start_frame = Frame(start_frame or 0)

    if resolve_plugin:
        self.resolve_plugins.add(resolve_plugin)

    self.toolbars.playback.stop()
    self.setWindowTitle(
        f'VSPreview: {self.display_name}' + (
            f', Arguments({", ".join(f"{k}={v}" for k, v in self.external_args)})'
            if self.external_args else ''
        )
    )

    self.statusbar.label.setText('Evaluating')
    self.script_path = script_path

    sys.path.append(str(self.script_path.parent))

    # Rewrite args so external args will be forwarded correctly
    argv_orig = None
    try:
        argv_orig = sys.argv
        sys.argv = [script_path.name]
    except AttributeError:
        pass

    try:
        if reloading:
            std_path_lib = SPath(logging.__file__).parent.parent
            std_path_dlls = std_path_lib.parent / 'DLLs'

            check_reloaded = set[str]()

            for module in set(sys.modules.values()) - PRELOADED_MODULES:
                if not hasattr(module, '__file__') or module.__file__ is None:
                    continue

                main_mod = module.__name__.split('.')[0]

                if main_mod in check_reloaded:
                    continue

                mod_file = SPath(module.__file__)

                if 'vspreview' in mod_file.parts:
                    continue

                if std_path_lib in mod_file.parents or std_path_dlls in mod_file.parents:
                    continue

                if not mod_file.exists() or not mod_file.is_file():
                    continue

                check_reloaded.add(main_mod)

            for module in check_reloaded:
                all_submodules = sorted([
                    k for k in sys.modules.keys()
                    if k == module or k.startswith(f'{module}.')
                ])

                for mod_name in all_submodules:
                    try:
                        if SPath(sys.modules[mod_name].__file__).stat().st_mtime > self.last_reload_time:
                            break
                    except Exception:
                        ...
                else:
                    continue

                try:
                    logging.info(f'Hot reloaded Python Package: "{module}"')
                    for mod_name in reversed(all_submodules):
                        sys.modules[mod_name] = reload_module(sys.modules[mod_name])
                except Exception as e:
                    logging.error(e)

        self.env = vpy.variables(
            dict(self.external_args),
            environment=vs.get_current_environment(),
            module_name="__vspreview__"
        ).result()
        self.env.module.__dict__['_monkey_runpy'] = random()
        self.env = vpy.script(self.script_path, environment=self.env).result()
    except Exception as e:
        return self.handle_error(e)
    finally:
        if argv_orig is not None:
            sys.argv = argv_orig
        sys.path.pop()
        self.last_reload_time = time()

    if len(vs.get_outputs()) == 0:
        logging.error('Script has no outputs set.')
        self.script_exec_failed = True
        self.handle_script_error('Script has no outputs set.', True)
        return

    reload_from_error = self.script_exec_failed and reloading
    self.script_exec_failed = False
    self.current_storage_path = (self.current_config_dir / self.script_path.stem).with_suffix('.yml')

    self.storage_not_found = not (
        self.current_storage_path.exists() and self.current_storage_path.read_text('utf8').strip()
    )

    load_error = None

    try:
        if self.storage_not_found or reload_from_error:
            self.load_storage()

        if not reloading or (self.outputs is None):
            self.toolbars.main.rescan_outputs()
            self.toolbars.playback.rescan_outputs()

        if not self.storage_not_found:
            self.load_storage()
    except Exception as e:
        load_error = e

    if not reloading:
        self.shortcuts.setup_shortcuts()
    self.apply_stylesheet()
    self.timeline.set_sizes()

    with self.env:
        vs.register_on_destroy(self.gc_collect)

    if load_error is None:
        self.autosave_timer.start(round(float(self.settings.autosave_interval) * 1000))

        if not reloading:
            self.switch_output(self.settings.output_index)
            if start_frame is not None:
                self.switch_frame(Frame(start_frame))
    else:
        error_string = "There was an error while loading the script!\n"

        logging.error(error_string + vpy.textwrap.indent(vpy.ExecutionFailed.extract_traceback(load_error), '| '))

        self.script_exec_failed = True

        return self.handle_script_error(
            f'{error_string}{vpy.textwrap.indent(str(load_error), " | ")}\nSee console output for details.', False
        )

    self.show()
    self.plugins.setup_ui()

load_storage

load_storage() -> None
Source code
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
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
@set_status_label('Loading...')
def load_storage(self) -> None:
    storage_paths = [self.global_storage_path, self.current_storage_path]

    if self.storage_not_found:
        logging.info('No storage found. Using defaults.')

        if not self.global_storage_path.exists():
            return

        storage_paths = storage_paths[:1]

    storage_contents = ''
    broken_storage = False
    global_length = 0
    for i, storage_path in enumerate(storage_paths):
        try:
            with io.open(storage_path, 'r', encoding='utf-8') as storage_file:
                version = storage_file.readline()
                if 'Version' not in version or any({
                    version.strip().endswith(f'@{v}') for v in self.BREAKING_CHANGES_VERSIONS
                }):
                    raise FileNotFoundError

                storage_contents += storage_file.read()
                storage_contents += '\n'

                if i == 0:
                    global_length = storage_contents.count('\n')
        except FileNotFoundError:
            if self.settings.force_old_storages_removal or i == 0:
                if storage_path.exists():
                    storage_path.unlink()
                    broken_storage = True
            else:
                logging.warning(
                    '\n\tThe storage was created on an old version of VSPreview.'
                    '\n\tSave any scening or other important info and delete it.'
                    '\n\tIf you want the program to silently delete old storages,'
                    '\n\tgo into settings or set --force-storage flag.'
                    '\n\t'
                    '\n\tStorage file location:'
                    '\n\t\t' + str(storage_path)
                )
                sys.exit(1)

    if broken_storage:
        return

    loader = yaml_Loader(storage_contents)
    try:
        loader.get_single_data()
    except YAMLError as exc:
        if isinstance(exc, MarkedYAMLError):
            if exc.problem_mark:
                line = exc.problem_mark.line + 1
                isglobal = line <= global_length
                if not isglobal:
                    line -= global_length
                logging.warning(
                    'Storage ({}) parsing failed on line {} column {}. \n({})\n'
                    'Failed to parse storage; it may be corrupted or from an old version of VSPreview.\n'
                    'Manually fix the file(s) or save any scening and other important info and delete them.\n'
                    '\n'
                    'Storage file locations:\n'
                    '{}'
                    .format(
                        'Global' if isglobal else 'Local',
                        line, exc.problem_mark.column + 1,
                        str(exc).partition('in "<unicode string>"')[0].strip(),
                        '\n'.join(str(p) for p in storage_paths)
                    )
                )
                sys.exit(1)
        else:
            logging.warning('Storage parsing failed. Using defaults.')
    finally:
        loader.dispose()

    if self.settings.color_management:
        assert self.app
        self.current_screen = self.app.primaryScreen()
        self.update_display_profile()

moveEvent

moveEvent(_move_event: QMoveEvent) -> None
Source code
989
990
991
992
993
994
995
def moveEvent(self, _move_event: QMoveEvent) -> None:
    if self.settings.color_management:
        assert self.app
        screen_number = self.app.primaryScreen()
        if self.current_screen != screen_number:
            self.current_screen = screen_number
            self.update_display_profile()

move_legacy_global_storage

move_legacy_global_storage() -> None
Source code
499
500
501
502
503
504
505
506
507
508
509
510
def move_legacy_global_storage(self) -> None:
    legacy_global_storage = self.global_config_dir / '.global.yml'

    if not legacy_global_storage.exists():
        return

    logging.warning(f'Moving legacy global storage file from \'{legacy_global_storage}\' to \'{self.global_storage_path}\'!')

    try:
        legacy_global_storage.rename(self.global_storage_path)
    except Exception as e:
        logging.error(f'Failed to move global storage file:\n\n{e}')

move_legacy_vspdir

move_legacy_vspdir() -> None
Source code
488
489
490
491
492
493
494
495
496
def move_legacy_vspdir(self) -> None:
    legacy_vspdir = self.VSP_GLOBAL_DIR_NAME / '.vspreview'

    if not legacy_vspdir.exists():
        return

    logging.warning(f'Moving legacy vspreview directory from \'{legacy_vspdir}\' to \'{self.global_config_dir}\'!')

    legacy_vspdir.move_dir(self.global_config_dir)

on_timeline_clicked

on_timeline_clicked(start: int) -> None
Source code
888
889
890
891
892
893
894
def on_timeline_clicked(self, start: int) -> None:
    if self.toolbars.playback.play_timer.isActive():
        self.toolbars.playback.stop()
        self.switch_frame(start)
        self.toolbars.playback.play()
    else:
        self.switch_frame(start)

on_zoom_changed

on_zoom_changed(
    text: str | None = None, bound_view: GraphicsView | None = None
) -> None
Source code
875
876
877
878
879
880
def on_zoom_changed(self, text: str | None = None, bound_view: GraphicsView | None = None) -> None:
    if not bound_view:
        return

    for view in self.bound_graphics_views[bound_view]:
        view.setZoom(bound_view.zoom_combobox.currentData())

pop_out_plugins

pop_out_plugins() -> None
Source code
264
265
266
267
268
269
270
271
272
def pop_out_plugins(self) -> None:
    left, right = self.main_split.sizes()
    if right:
        new_sizes = [left + right, 0]
    else:
        min_right = int((left + right) * 0.2)
        new_sizes = [min(left, left + right - min_right), max(right, min_right)]
    self.main_split.setSizes(new_sizes)
    self.plugins.update()

refresh_graphics_views

refresh_graphics_views() -> None
Source code
847
848
849
def refresh_graphics_views(self) -> None:
    for graphics_view in self.graphics_views:
        graphics_view.setup_view()

refresh_video_outputs

refresh_video_outputs() -> None
Source code
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
def refresh_video_outputs(self) -> None:
    if not self.outputs:
        return

    playback_active = self.toolbars.playback.play_timer.isActive()

    if playback_active:
        self.toolbars.playback.stop()

    self.init_outputs()

    self.switch_output(self.toolbars.main.outputs_combobox.currentIndex())

    if playback_active:
        self.toolbars.playback.play()

register_graphic_view

register_graphic_view(view: GraphicsView) -> None
Source code
867
868
869
870
871
872
873
def register_graphic_view(self, view: GraphicsView) -> None:
    self.bound_graphics_views[view] = {view}

    view.zoom_combobox.currentTextChanged.connect(partial(self.on_zoom_changed, bound_view=view))

    view.zoom_combobox.setModel(GeneralModel[float](self.settings.zoom_levels))
    view.zoom_combobox.setCurrentIndex(self.settings.zoom_default_index)

reload_script

reload_script() -> None
Source code
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
def reload_script(self) -> None:
    self.reload_before_signal.emit()

    self.dump_storage()

    self.clean_core_references()

    old_environment = get_current_environment()

    self.clear_monkey_runpy()
    make_environment()
    dispose_environment(old_environment)
    self.gc_collect()

    try:
        self.load_script(self.script_path, self.external_args, True, None, self.display_name)
    finally:
        self.clear_monkey_runpy()

    self.reload_after_signal.emit()

    self.show_message('Reloaded successfully')

set_node_info

set_node_info(node_type: type, index: int, **kwargs: Any) -> None
Source code
958
959
960
961
962
963
964
def set_node_info(self, node_type: type, index: int, **kwargs: Any) -> None:
    base = self.user_output_info[node_type]

    if index not in base:
        base[index] = {**kwargs}
    else:
        base[index] |= kwargs

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)

set_temporary_scenes

set_temporary_scenes(scenes: list[SceningList]) -> None
Source code
948
949
def set_temporary_scenes(self, scenes: list[SceningList]) -> None:
    self.temporary_scenes = scenes

setup_ui

setup_ui() -> None
Source code
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
def setup_ui(self) -> None:
    self.central_widget = ExtendedWidget(self)
    self.main_layout = VBoxLayout(self.central_widget)
    self.setCentralWidget(self.central_widget)

    self.graphics_view = MainVideoOutputGraphicsView(self, self.central_widget)

    DragNavigator(self, self.graphics_view)

    self.timeline = Timeline(self.central_widget)

    self.plugins_tab = QTabWidget(self.central_widget)
    self.plugins_tab.currentChanged.connect(lambda x: self.plugins.update())

    self.main_split = CentralSplitter(self, QtCore.Qt.Orientation.Horizontal)
    self.main_split.addWidget(self.graphics_view)
    self.main_split.addWidget(self.plugins_tab)
    self.main_split.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)

    HBoxLayout(self.main_layout, [self.main_split])
    self.main_layout.addWidget(self.timeline)

    # status bar
    self.statusbar = StatusBar(self.central_widget)

    for name in self.statusbar.label_names:
        self.statusbar.__setattr__(name, QLabel(self.central_widget))

    self.statusbar.addWidgets([
        getattr(self.statusbar, name) for name in self.statusbar.label_names
    ])

    self.statusbar.addPermanentWidget(self.statusbar.label)

    self.setStatusBar(self.statusbar)

    # dialogs
    self.script_error_dialog = ScriptErrorDialog(self)

    self.autosave_timer = Timer(timeout=self.dump_storage_async)
    self.reload_signal.connect(self.autosave_timer.stop)

showEvent

showEvent(event: QShowEvent) -> None
Source code
973
974
975
976
977
978
def showEvent(self, event: QShowEvent) -> None:
    super().showEvent(event)
    for graphics_view in self.graphics_views:
        graphics_view.setSizePolicy(self.EVENT_POLICY)

    self.main_split.setSizePolicy(self.EVENT_POLICY)

show_message

show_message(message: str) -> None
Source code
915
916
917
918
919
def show_message(self, message: str) -> None:
    self.statusbar.clearMessage()
    self.statusbar.showMessage(
        message, round(float(self.settings.statusbar_message_timeout) * 1000)
    )

switch_frame

switch_frame(
    pos: Frame | int, *, render_frame: bool | Iterable[VideoFrame | None] = True
) -> None
Source code
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
def switch_frame(
    self, pos: Frame | int, *, render_frame: bool | Iterable[vs.VideoFrame | None] = True
) -> None:
    frame = Frame(pos)

    if (not 0 <= frame < self.current_output.total_frames):
        return

    if render_frame:
        if isinstance(render_frame, bool):
            self.current_output.render_frame(frame, output_colorspace=self.display_profile)
        else:
            self.current_output.render_frame(
                frame, *render_frame, output_colorspace=self.display_profile  # type: ignore
            )

    self.current_output.last_showed_frame = frame

    self.timeline.cursor_x = frame

    for toolbar in self.toolbars:
        toolbar.on_current_frame_changed(frame)

    self.plugins.on_current_frame_changed(frame)

    self.statusbar.frame_props_label.setText(
        f"Type: {get_prop(self.current_output.props, '_PictType', str, None, '?')}"
    )

switch_output

switch_output(value: int | VideoOutput) -> None
Source code
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
def switch_output(self, value: int | VideoOutput) -> None:
    if not self.outputs or len(self.outputs) == 0:
        return

    if isinstance(value, VideoOutput):
        index = self.outputs.index_of(value)
    else:
        index = value

    if index < 0:
        index = len(self.outputs) + index

    if index < 0 or index >= len(self.outputs):
        return

    prev_index = self.toolbars.main.outputs_combobox.currentIndex()

    self.toolbars.playback.stop()

    # current_output relies on outputs_combobox
    self.toolbars.main.on_current_output_changed(index, prev_index)

    self.switch_frame(self.current_output.last_showed_frame)

    self.refresh_graphics_views()

    self.timeline.update_notches()

    for toolbar in self.toolbars[1:]:
        toolbar.on_current_output_changed(index, prev_index)

    self.plugins.on_current_output_changed(index, prev_index)

    self.update_statusbar_output_info()

update_display_profile

update_display_profile() -> None
Source code
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
def update_display_profile(self) -> None:
    if sys.platform == 'win32':
        assert self.app and _imagingcms

        screen_name = self.current_screen.name()

        dc = win32gui.CreateDC('DISPLAY', screen_name, None)

        logging.info(f'Changed screen: {screen_name}')

        icc_path = _imagingcms.get_display_profile_win32(dc, 1)

        if icc_path is not None:
            with open(icc_path, 'rb') as icc:
                self.display_profile = QColorSpace.fromIccProfile(icc.read())

    if hasattr(self, 'current_output') and self.current_output is not None and self.display_profile is not None:
        self.switch_frame(self.current_output.last_showed_frame)

update_statusbar_output_info

update_statusbar_output_info(output: VideoOutput | None = None) -> None
Source code
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
def update_statusbar_output_info(self, output: VideoOutput | None = None) -> None:
    output = output or self.current_output
    fmt = output.source.clip.format
    assert fmt

    self.statusbar.clearMessage()

    self.statusbar.total_frames_label.setText(f'{output.total_frames} frames ')
    self.statusbar.duration_label.setText(f'{output.total_time} ')
    self.statusbar.resolution_label.setText(f'{output.width}x{output.height} ')
    self.statusbar.pixel_format_label.setText(f'{fmt.name} ')

    if output.got_timecodes:
        times = sorted(set(output.timecodes), reverse=True)

        if len(times) >= 2:
            return self.statusbar.fps_label.setText(
                f'VFR {",".join(f"{float(fps):.3f}" for fps in times)} fps '
            )

    if output.fps_den != 0:
        return self.statusbar.fps_label.setText(
            f'{output.fps_num}/{output.fps_den} = {output.fps_num / output.fps_den:.3f} fps '
        )

    self.statusbar.fps_label.setText(f'VFR {output.fps_num}/{output.fps_den} fps ')

update_timecodes_info

update_timecodes_info(
    index: int,
    timecodes: (
        str
        | SPath
        | dict[
            tuple[int | None, int | None], float | tuple[int, int] | Fraction
        ]
        | list[Fraction]
    ),
    den: int | None = None,
) -> None
Source code
951
952
953
954
955
956
def update_timecodes_info(
    self, index: int, timecodes: str | SPath | dict[
        tuple[int | None, int | None], float | tuple[int, int] | Fraction
    ] | list[Fraction], den: int | None = None
) -> None:
    self.timecodes[index] = (timecodes, den)