Skip to content

toolbar

Classes:

MiscToolbar

MiscToolbar(main: MainWindow)

Bases: AbstractToolbar

Methods:

Attributes:

Source code
44
45
46
47
48
49
50
51
52
53
def __init__(self, main: MainWindow) -> None:
    super().__init__(main, MiscSettings(self))

    self.setup_ui()

    self.save_file_types = {'Single Image (*.png)': self.save_as_png}

    self.main.settings.autosave_control.valueChanged.connect(self.on_autosave_interval_changed)

    self.set_qobject_names()

class_storable_attrs class-attribute instance-attribute

class_storable_attrs = tuple[str, ...](('settings', 'visibility'))

hlayout instance-attribute

hlayout: HBoxLayout

is_notches_visible property

is_notches_visible: bool

main instance-attribute

main: MainWindow = main

name instance-attribute

name: str = __name__[:-7]

notches_changed class-attribute instance-attribute

notches_changed = pyqtSignal(ExtendedWidget)

num_keys class-attribute instance-attribute

num_keys = [
    Key_1,
    Key_2,
    Key_3,
    Key_4,
    Key_5,
    Key_6,
    Key_7,
    Key_8,
    Key_9,
    Key_0,
]

save_file_types instance-attribute

save_file_types = {'Single Image (*.png)': save_as_png}

settings instance-attribute

settings: MiscSettings

storable_attrs class-attribute instance-attribute

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

toggle_button instance-attribute

toggle_button = PushButton(name, self, checkable=True, clicked=on_toggle)

visibility instance-attribute

visibility = False

vlayout instance-attribute

vlayout: VBoxLayout

copy_frame_to_clipboard

copy_frame_to_clipboard() -> None
Source code
161
162
163
def copy_frame_to_clipboard(self) -> None:
    self.main.clipboard.setPixmap(self.main.current_scene.pixmap())
    self.main.show_message('Current frame successfully copied to clipboard')

crop_active_onchange

crop_active_onchange(checked: bool) -> None
Source code
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
def crop_active_onchange(self, checked: bool) -> None:
    is_absolute = not not self.crop_mode_combox.currentIndex()

    self.crop_top_spinbox.setEnabled(checked)
    self.crop_left_spinbox.setEnabled(checked)

    self.crop_bottom_spinbox.setEnabled(checked and not is_absolute)
    self.crop_right_spinbox.setEnabled(checked and not is_absolute)

    self.crop_width_spinbox.setEnabled(checked and is_absolute)
    self.crop_height_spinbox.setEnabled(checked and is_absolute)

    self.crop_mode_combox.setEnabled(checked)

    self.crop_copycommand_button.setEnabled(checked)

    self.update_crop()

crop_bottom_onchange

crop_bottom_onchange(value: int) -> None
Source code
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
def crop_bottom_onchange(self, value: int) -> None:
    if not self.crop_active_switch.isChecked():
        return

    if self.crop_mode_combox.currentIndex():
        return

    height = self.main.current_output.height
    offset = height - self.crop_top_spinbox.value()

    if offset - value < 1:
        qt_silent_call(self.crop_bottom_spinbox.setValue, offset - 1)
        return

    qt_silent_call(self.crop_height_spinbox.setValue, offset - value)

    self.update_crop()

crop_copycommand_onclick

crop_copycommand_onclick() -> None
Source code
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
def crop_copycommand_onclick(self) -> None:
    is_absolute = self.crop_mode_combox.currentIndex()

    crop_top = self.crop_top_spinbox.value()
    crop_left = self.crop_left_spinbox.value()

    if not is_absolute:
        crop_bottom = self.crop_bottom_spinbox.value()
        crop_right = self.crop_right_spinbox.value()

        text = f'.std.Crop({crop_left}, {crop_right}, {crop_top}, {crop_bottom})'
    else:
        crop_width = self.crop_width_spinbox.value()
        crop_height = self.crop_height_spinbox.value()

        text = f'.std.CropAbs({crop_width}, {crop_height}, {crop_left}, {crop_top})'

    self.main.clipboard.setText(text)

crop_height_onchange

crop_height_onchange(value: int) -> None
Source code
405
406
407
408
409
410
411
412
413
414
415
416
417
418
def crop_height_onchange(self, value: int) -> None:
    if not self.crop_active_switch.isChecked():
        return

    if self.crop_mode_combox.currentIndex():
        height = self.main.current_output.height
        offset = height - self.crop_top_spinbox.value()

        qt_silent_call(self.crop_bottom_spinbox.setValue, offset - value)

        if offset - value < 1:
            qt_silent_call(self.crop_top_spinbox.setValue, height - value)

    self.update_crop()

crop_left_onchange

crop_left_onchange(value: int) -> None
Source code
339
340
341
342
343
344
345
346
347
348
349
350
351
352
def crop_left_onchange(self, value: int) -> None:
    if not self.crop_active_switch.isChecked():
        return

    width = self.main.current_output.width
    offset = width - self.crop_right_spinbox.value()

    if offset - value < 1:
        qt_silent_call(self.crop_left_spinbox.setValue, offset - 1)
        return

    qt_silent_call(self.crop_width_spinbox.setValue, offset - value)

    self.update_crop()

crop_mode_onchange

crop_mode_onchange(crop_mode_idx: int) -> None
Source code
313
314
315
316
317
318
319
320
321
322
def crop_mode_onchange(self, crop_mode_idx: int) -> None:
    is_absolute = bool(crop_mode_idx)

    self.crop_bottom_spinbox.setEnabled(not is_absolute)
    self.crop_right_spinbox.setEnabled(not is_absolute)

    self.crop_width_spinbox.setEnabled(is_absolute)
    self.crop_height_spinbox.setEnabled(is_absolute)

    self.update_crop()

crop_right_onchange

crop_right_onchange(value: int) -> None
Source code
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
def crop_right_onchange(self, value: int) -> None:
    if not self.crop_active_switch.isChecked():
        return

    if self.crop_mode_combox.currentIndex():
        return

    width = self.main.current_output.width
    offset = width - self.crop_left_spinbox.value()

    if offset - value < 1:
        qt_silent_call(self.crop_right_spinbox.setValue, offset - 1)
        return

    qt_silent_call(self.crop_width_spinbox.setValue, offset - value)

    self.update_crop()

crop_top_onchange

crop_top_onchange(value: int) -> None
Source code
324
325
326
327
328
329
330
331
332
333
334
335
336
337
def crop_top_onchange(self, value: int) -> None:
    if not self.crop_active_switch.isChecked():
        return

    height = self.main.current_output.height
    offset = height - self.crop_bottom_spinbox.value()

    if offset - value < 1:
        qt_silent_call(self.crop_top_spinbox.setValue, offset - 1)
        return

    qt_silent_call(self.crop_height_spinbox.setValue, offset - value)

    self.update_crop()

crop_width_onchange

crop_width_onchange(value: int) -> None
Source code
390
391
392
393
394
395
396
397
398
399
400
401
402
403
def crop_width_onchange(self, value: int) -> None:
    if not self.crop_active_switch.isChecked():
        return

    if self.crop_mode_combox.currentIndex():
        width = self.main.current_output.width
        offset = width - self.crop_left_spinbox.value()

        qt_silent_call(self.crop_right_spinbox.setValue, offset - value)

        if offset - value < 1:
            qt_silent_call(self.crop_left_spinbox.setValue, width - value)

    self.update_crop()

get_notches

get_notches() -> Notches
Source code
405
406
407
def get_notches(self) -> Notches:
    from .custom import Notches
    return Notches()

get_separator

get_separator(horizontal: bool = False) -> QFrame
Source code
318
319
320
321
322
def get_separator(self, horizontal: bool = False) -> QFrame:
    separator = QFrame(self)
    separator.setFrameShape(QFrame.Shape.HLine if horizontal else QFrame.Shape.VLine)
    separator.setFrameShadow(QFrame.Shadow.Sunken)
    return separator

init_notches

init_notches(main: MainWindow = ...) -> None
Source code
402
403
def init_notches(self, main: MainWindow = ...) -> None:
    self.notches_changed.connect(main.timeline.update_notches)

on_autosave_interval_changed

on_autosave_interval_changed(new_value: Time | None) -> None
Source code
165
166
167
168
169
170
171
def on_autosave_interval_changed(self, new_value: Time | None) -> None:
    if new_value is None:
        return
    if new_value == Time(seconds=0):
        self.main.autosave_timer.stop()
    else:
        self.main.autosave_timer.start(round(float(new_value) * 1000))

on_current_frame_changed

on_current_frame_changed(frame: Frame) -> None
Source code
471
472
def on_current_frame_changed(self, frame: Frame) -> None:
    pass

on_current_output_changed

on_current_output_changed(index: int, prev_index: int) -> None
Source code
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
257
258
def on_current_output_changed(self, index: int, prev_index: int) -> None:
    if index != prev_index:
        self.update_crop(prev_index)

    curr = self.main.current_output
    crop = curr.crop_values
    ar = curr.ar_values

    self.crop_top_spinbox.setMaximum(curr.height - 1)
    self.crop_bottom_spinbox.setMaximum(curr.height - 1)
    self.crop_left_spinbox.setMaximum(curr.width - 1)
    self.crop_right_spinbox.setMaximum(curr.width - 1)
    self.crop_width_spinbox.setMaximum(curr.width)
    self.crop_height_spinbox.setMaximum(curr.height)

    qt_silent_call(self.crop_top_spinbox.setValue, crop.top)
    qt_silent_call(self.crop_bottom_spinbox.setValue, curr.height - crop.height - crop.top)
    qt_silent_call(self.crop_left_spinbox.setValue, crop.left)
    qt_silent_call(self.crop_right_spinbox.setValue, curr.width - crop.width - crop.left)
    qt_silent_call(self.crop_width_spinbox.setValue, crop.width)
    qt_silent_call(self.crop_height_spinbox.setValue, crop.height)

    self.crop_active_switch.setChecked(not crop.active)
    self.ar_active_switch.setChecked(not ar.active)
    self.crop_active_switch.click()

    self.crop_mode_combox.setCurrentIndex(int(crop.is_absolute))

on_save_frame_as_clicked

on_save_frame_as_clicked(checked: bool | None = None) -> None
Source code
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
211
212
213
214
215
216
217
def on_save_frame_as_clicked(self, checked: bool | None = None) -> None:
    from vstools import video_heuristics

    curr_out = self.main.current_output.source.clip
    fmt = curr_out.format
    assert fmt

    filter_str = ''.join([file_type + ';;' for file_type in self.save_file_types.keys()])[0:-2]

    template = self.save_template_lineedit.text()

    props = self.main.current_output.props

    heuristics = video_heuristics(self.main.current_output.source.clip, props)

    substitutions = {
        **props, **heuristics,
        'format': fmt.name,
        'fps_den': self.main.current_output.fps_den,
        'fps_num': self.main.current_output.fps_num,
        'width': self.main.current_output.width,
        'height': self.main.current_output.height,
        'script_name': self.main.script_path.stem,
        'index': self.main.current_output.index,
        'node_name': self.main.current_output.name,
        'frame': self.main.current_output.last_showed_frame,
        'total_frames': self.main.current_output.total_frames
    }

    try:
        suggested_path_str = template.format(**substitutions)
    except KeyError:
        invalid_keys = [key.split('}')[0] for key in template.split('{')[1:] if key.split('}')[0] not in substitutions]

        self.main.show_message(f'Save name template is invalid.{f" Invalid key(s): <{', '.join(invalid_keys)}>" if invalid_keys else ""}')

        return

    save_path_str, file_type = QFileDialog.getSaveFileName(
        self.main, 'Save as', suggested_path_str, filter_str
    )
    try:
        self.save_file_types[file_type](Path(save_path_str))
    except KeyError:
        pass

on_show_debug_changed

on_show_debug_changed(state: CheckState) -> None
Source code
219
220
221
222
223
224
225
226
227
def on_show_debug_changed(self, state: Qt.CheckState) -> None:
    assert hasattr(self.main.toolbars, 'debug')

    if state == Qt.CheckState.Checked:
        self.main.toolbars.debug.toggle_button.setVisible(True)
    elif state == Qt.CheckState.Unchecked:
        if self.main.toolbars.debug.toggle_button.isChecked():
            self.main.toolbars.debug.toggle_button.click()
        self.main.toolbars.debug.toggle_button.setVisible(False)

on_toggle

on_toggle(new_state: bool) -> None
Source code
461
462
463
464
465
466
467
468
469
def on_toggle(self, new_state: bool) -> None:
    if new_state == self.visibility:
        return

    # invoking order matters
    self.setVisible(new_state)
    self.visibility = new_state
    self.toggle_button.setChecked(new_state)
    self.resize_main_window(new_state)

resize_main_window

resize_main_window(expanding: bool) -> None
Source code
481
482
483
484
485
486
487
488
489
def resize_main_window(self, expanding: bool) -> None:
    if self.main.windowState() in {Qt.WindowState.WindowMaximized, Qt.WindowState.WindowFullScreen}:
        return

    if expanding:
        self.main.resize(self.main.width(), self.main.height() + self.height() + round(6 * self.main.display_scale))
    if not expanding:
        self.main.resize(self.main.width(), self.main.height() - self.height() - round(6 * self.main.display_scale))
        self.main.timeline.update()

save_as_png

save_as_png(path: Path) -> None
Source code
229
230
def save_as_png(self, path: Path) -> None:
    self.main.current_scene.pixmap().save(str(path), 'PNG', self.main.settings.png_compression_level)

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)

setup_ui

setup_ui() -> None
Source code
 55
 56
 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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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
158
159
def setup_ui(self) -> None:
    super().setup_ui()

    self.reload_script_button = PushButton(
        'Reload Script', self, clicked=self.main.reload_script, hidden=not self.main.reload_enabled
    )

    self.save_storage_button = PushButton(
        'Save Storage', self, clicked=partial(self.main.dump_storage_async)
    )

    self.autosave_checkbox = CheckBox('Autosave', self, checked=True)

    self.copy_frame_button = PushButton('Copy Frame', self, clicked=self.copy_frame_to_clipboard)

    self.save_frame_as_button = PushButton('Save Frame as', self, clicked=self.on_save_frame_as_clicked)

    self.save_template_lineedit = LineEdit(
        self.settings.SAVE_TEMPLATE, self, text=self.settings.SAVE_TEMPLATE,
        tooltip='''
            Available placeholders:
                {format}, {fps_den}, {fps_num}, {frame},
                {height}, {index}, {node_name}, {matrix},
                {primaries}, {range}, {script_name}, {total_frames},
                {transfer}, {width}.
            Frame props can be accessed as well using their names.
        '''.replace(' ' * 16, ' ').strip()
    )

    first_layer = [self.autosave_checkbox, self.get_separator()]

    if 'debug' in self.main.toolbars.toolbar_names:
        self.show_debug_checkbox = CheckBox('Show Debug Toolbar', self, stateChanged=self.on_show_debug_changed)
        first_layer.append(self.show_debug_checkbox)
        self.__slots__ = tuple([*self.__slots__, 'show_debug_checkbox'])  # type: ignore

    VBoxLayout(self.hlayout, [
        HBoxLayout([*first_layer, Stretch()]),
        HBoxLayout([
            *(
                [self.reload_script_button, self.get_separator()]
                if self.main.reload_enabled else
                []
            ),
            self.save_storage_button, self.get_separator(),
            self.copy_frame_button, Stretch()
        ]),
        HBoxLayout([self.save_frame_as_button, self.save_template_lineedit, Stretch()])
    ])

    self.hlayout.addStretch()
    self.hlayout.addStretch()

    self.ar_active_switch = Switch(
        10, checked=False, clicked=lambda active: (ArInfo.active.__set__(ArInfo, active), self.update_sar()),
        tooltip='Toggle respect SAR properties'
    )

    self.crop_active_switch = Switch(10, 22, checked=True, clicked=self.crop_active_onchange)

    self.crop_top_spinbox = SpinBox(None, 0, 2 ** 16, valueChanged=self.crop_top_onchange)
    self.crop_left_spinbox = SpinBox(None, 0, 2 ** 16, valueChanged=self.crop_left_onchange)
    self.crop_bottom_spinbox = SpinBox(None, 0, 2 ** 16, valueChanged=self.crop_bottom_onchange)
    self.crop_right_spinbox = SpinBox(None, 0, 2 ** 16, valueChanged=self.crop_right_onchange)
    self.crop_width_spinbox = SpinBox(None, 1, 2 ** 16, valueChanged=self.crop_width_onchange)
    self.crop_height_spinbox = SpinBox(None, 1, 2 ** 16, valueChanged=self.crop_height_onchange)

    self.crop_copycommand_button = PushButton('Copy cropping command', clicked=self.crop_copycommand_onclick)

    self.crop_mode_combox = ComboBox[str](
        self, model=GeneralModel[str](['relative', 'absolute']),
        currentIndex=0, sizeAdjustPolicy=QComboBox.SizeAdjustPolicy.AdjustToContents
    )
    self.crop_mode_combox.currentIndexChanged.connect(self.crop_mode_onchange)

    self.crop_active_switch.click()

    HBoxLayout(self.hlayout, [
        VBoxLayout([
            HBoxLayout([
                QLabel('Toggle SAR'), self.ar_active_switch,
            ], spacing=0)
        ]),
        VBoxLayout([
            HBoxLayout([
                QLabel('Top'), self.crop_top_spinbox, QSpacerItem(35, 10)
            ], alignment=Qt.AlignmentFlag.AlignCenter, spacing=0),
            HBoxLayout([
                QLabel('Left'), self.crop_left_spinbox,
                self.crop_active_switch,
                self.crop_right_spinbox, QLabel('Right')
            ], alignment=Qt.AlignmentFlag.AlignCenter, spacing=5),
            HBoxLayout([
                QLabel('Bottom'), self.crop_bottom_spinbox, QSpacerItem(51, 10)
            ], alignment=Qt.AlignmentFlag.AlignCenter, spacing=0)
        ]),
        VBoxLayout([
            HBoxLayout([QLabel('Cropping Type:'), self.crop_mode_combox]),
            HBoxLayout([
                QLabel('Width'), self.crop_width_spinbox,
                QLabel('Height'), self.crop_height_spinbox
            ], spacing=0),
            HBoxLayout([self.crop_copycommand_button])
        ])
    ])

update_crop

update_crop(index: int | None = None) -> None
Source code
283
284
285
286
287
288
289
290
291
292
293
def update_crop(self, index: int | None = None) -> None:
    if not hasattr(self.main, 'current_output') or not self.main.outputs:
        return

    output = self.main.current_output if index is None else self.main.outputs[index]

    output.update_graphic_item(None, CroppingInfo(
        self.crop_top_spinbox.value(), self.crop_left_spinbox.value(),
        self.crop_width_spinbox.value(), self.crop_height_spinbox.value(),
        self.crop_active_switch.isChecked(), bool(self.crop_mode_combox.currentIndex())
    ), graphics_scene_item=self.main.current_output.graphics_scene_item)

update_sar

update_sar(index: int | None = None) -> None
Source code
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
def update_sar(self, index: int | None = None) -> None:
    if not hasattr(self.main, 'current_output') or not self.main.outputs:
        return

    output = self.main.current_output if index is None else self.main.outputs[index]

    if not output._stateset or output.props is None:
        return

    try:
        sar = (
            max(get_prop(output.props, '_SARNum', int), 1),
            max(get_prop(output.props, '_SARDen', int), 1)
        )
    except FramePropError:
        logging.error('Failed to get SAR properties')
        return

    output.update_graphic_item(
        None, None, ArInfo(*sar),
        graphics_scene_item=self.main.current_output.graphics_scene_item
    )