def setup_ui(self) -> None: from vspreview.widgets import GraphicsView # mainWindow.resize(1300, 808) self.central_widget = Qt.QWidget(self) self.main_layout = Qt.QVBoxLayout(self.central_widget) self.setCentralWidget(self.central_widget) self.graphics_view = GraphicsView(self.central_widget) self.graphics_view.setBackgroundBrush(self.palette().brush( Qt.QPalette.Window)) self.graphics_view.setSizePolicy(Qt.QSizePolicy.Fixed, Qt.QSizePolicy.Fixed) self.graphics_view.setDragMode(Qt.QGraphicsView.ScrollHandDrag) self.graphics_view.setTransformationAnchor( GraphicsView.AnchorUnderMouse) self.main_layout.addWidget(self.graphics_view) self.timeline = Timeline(self.central_widget) self.main_layout.addWidget(self.timeline) # status bar self.statusbar = StatusBar(self.central_widget) self.statusbar.total_frames_label = Qt.QLabel(self.central_widget) self.statusbar.total_frames_label.setObjectName( 'MainWindow.statusbar.total_frames_label') self.statusbar.addWidget(self.statusbar.total_frames_label) self.statusbar.duration_label = Qt.QLabel(self.central_widget) self.statusbar.duration_label.setObjectName( 'MainWindow.statusbar.duration_label') self.statusbar.addWidget(self.statusbar.duration_label) self.statusbar.resolution_label = Qt.QLabel(self.central_widget) self.statusbar.resolution_label.setObjectName( 'MainWindow.statusbar.resolution_label') self.statusbar.addWidget(self.statusbar.resolution_label) self.statusbar.pixel_format_label = Qt.QLabel(self.central_widget) self.statusbar.pixel_format_label.setObjectName( 'MainWindow.statusbar.pixel_format_label') self.statusbar.addWidget(self.statusbar.pixel_format_label) self.statusbar.fps_label = Qt.QLabel(self.central_widget) self.statusbar.fps_label.setObjectName( 'MainWindow.statusbar.fps_label') self.statusbar.addWidget(self.statusbar.fps_label) self.statusbar.label = Qt.QLabel(self.central_widget) self.statusbar.label.setObjectName('MainWindow.statusbar.label') self.statusbar.addPermanentWidget(self.statusbar.label) self.setStatusBar(self.statusbar) # dialogs self.script_error_dialog = ScriptErrorDialog(self)
class MainWindow(AbstractMainWindow): # those are defaults that can be overriden at runtime or used as fallbacks ALWAYS_SHOW_SCENE_MARKS = False AUTOSAVE_INTERVAL = 60 * 1000 # s BASE_PPI = 96 # PPI BENCHMARK_CLEAR_CACHE = False BENCHMARK_REFRESH_INTERVAL = 150 # ms CHECKERBOARD_ENABLED = True CHECKERBOARD_TILE_COLOR_1 = Qt.Qt.white CHECKERBOARD_TILE_COLOR_2 = Qt.Qt.lightGray CHECKERBOARD_TILE_SIZE = 8 # px DARK_THEME = True FPS_AVERAGING_WINDOW_SIZE = FrameInterval(100) FPS_REFRESH_INTERVAL = 150 # ms LOG_LEVEL = logging.DEBUG OPENGL_RENDERING = False ORDERED_OUTPUTS = False OUTPUT_INDEX = 0 PLAY_BUFFER_SIZE = FrameInterval(get_usable_cpus_count()) PNG_COMPRESSION_LEVEL = 0 # 0 - 100 SAVE_TEMPLATE = '{script_name}_{frame}' SEEK_STEP = 1 # frames STATUSBAR_MESSAGE_TIMEOUT = 3 * 1000 # s STORAGE_BACKUPS_COUNT = 2 SYNC_OUTPUTS = False # it's allowed to stretch target interval betweewn notches by N% at most TIMELINE_LABEL_NOTCHES_MARGIN = 20 # % TIMELINE_MODE = 'frame' TOGGLE_TOOLBAR = False VSP_DIR_NAME = '.vspreview' # used for formats with subsampling VS_OUTPUT_RESIZER = Output.Resizer.Bicubic VS_OUTPUT_MATRIX = Output.Matrix.BT709 VS_OUTPUT_TRANSFER = Output.Transfer.BT709 VS_OUTPUT_PRIMARIES = Output.Primaries.BT709 VS_OUTPUT_RANGE = Output.Range.LIMITED VS_OUTPUT_CHROMALOC = Output.ChromaLoc.LEFT VS_OUTPUT_PREFER_PROPS = True VS_OUTPUT_RESIZER_KWARGS = {} # type: Mapping[str, str] BENCHMARK_FRAME_DATA_SHARING_FIX = True DEBUG_PLAY_FPS = False DEBUG_TOOLBAR = False DEBUG_TOOLBAR_BUTTONS_PRINT_STATE = False yaml_tag = '!MainWindow' storable_attrs = [ 'toolbars', ] __slots__ = storable_attrs + [ 'app', 'display_scale', 'clipboard', 'script_path', 'save_on_exit', 'timeline', 'main_layout', 'graphics_scene', 'graphics_view', 'script_error_dialog', 'central_widget', 'statusbar', 'opengl_widget', 'external_args', 'script_exec_failed' ] def __init__(self) -> None: from qdarkstyle import load_stylesheet_pyqt5 super().__init__() # logging # ??? self.app = Qt.QApplication.instance() if self.DARK_THEME: self.app.setStyleSheet( self.patch_dark_stylesheet(load_stylesheet_pyqt5())) self.ensurePolished() self.display_scale = self.app.primaryScreen().logicalDotsPerInch( ) / self.BASE_PPI self.setWindowTitle('VSPreview') self.move(400, 0) self.setup_ui() # global self.clipboard = self.app.clipboard() self.external_args: List[str] = [] self.script_path = Path() self.save_on_exit = True self.script_exec_failed = False # graphics view self.graphics_scene = Qt.QGraphicsScene(self) self.graphics_view.setScene(self.graphics_scene) self.opengl_widget = None if self.OPENGL_RENDERING: self.opengl_widget = Qt.QOpenGLWidget() self.graphics_view.setViewport(self.opengl_widget) self.graphics_view.wheelScrolled.connect(self.on_wheel_scrolled) # timeline self.timeline.clicked.connect(self.switch_frame) # init toolbars and outputs self.toolbars = Toolbars(self) self.main_layout.addWidget(self.toolbars.main) for toolbar in self.toolbars: self.main_layout.addWidget(toolbar) self.toolbars.main.layout().addWidget(toolbar.toggle_button) set_qobject_names(self) self.setObjectName('MainWindow') def setup_ui(self) -> None: from vspreview.widgets import GraphicsView # mainWindow.resize(1300, 808) self.central_widget = Qt.QWidget(self) self.main_layout = Qt.QVBoxLayout(self.central_widget) self.setCentralWidget(self.central_widget) self.graphics_view = GraphicsView(self.central_widget) self.graphics_view.setBackgroundBrush(self.palette().brush( Qt.QPalette.Window)) self.graphics_view.setSizePolicy(Qt.QSizePolicy.Fixed, Qt.QSizePolicy.Fixed) self.graphics_view.setDragMode(Qt.QGraphicsView.ScrollHandDrag) self.graphics_view.setTransformationAnchor( GraphicsView.AnchorUnderMouse) self.main_layout.addWidget(self.graphics_view) self.timeline = Timeline(self.central_widget) self.main_layout.addWidget(self.timeline) # status bar self.statusbar = StatusBar(self.central_widget) self.statusbar.total_frames_label = Qt.QLabel(self.central_widget) self.statusbar.total_frames_label.setObjectName( 'MainWindow.statusbar.total_frames_label') self.statusbar.addWidget(self.statusbar.total_frames_label) self.statusbar.duration_label = Qt.QLabel(self.central_widget) self.statusbar.duration_label.setObjectName( 'MainWindow.statusbar.duration_label') self.statusbar.addWidget(self.statusbar.duration_label) self.statusbar.resolution_label = Qt.QLabel(self.central_widget) self.statusbar.resolution_label.setObjectName( 'MainWindow.statusbar.resolution_label') self.statusbar.addWidget(self.statusbar.resolution_label) self.statusbar.pixel_format_label = Qt.QLabel(self.central_widget) self.statusbar.pixel_format_label.setObjectName( 'MainWindow.statusbar.pixel_format_label') self.statusbar.addWidget(self.statusbar.pixel_format_label) self.statusbar.fps_label = Qt.QLabel(self.central_widget) self.statusbar.fps_label.setObjectName( 'MainWindow.statusbar.fps_label') self.statusbar.addWidget(self.statusbar.fps_label) self.statusbar.label = Qt.QLabel(self.central_widget) self.statusbar.label.setObjectName('MainWindow.statusbar.label') self.statusbar.addPermanentWidget(self.statusbar.label) self.setStatusBar(self.statusbar) # dialogs self.script_error_dialog = ScriptErrorDialog(self) def patch_dark_stylesheet(self, stylesheet: str) -> str: return stylesheet \ + ' QGraphicsView { border: 0px; padding: 0px; }' \ + ' QToolButton { padding: 0px; }' def load_script(self, script_path: Path, external_args: str = '', reloading=False) -> None: import shlex from traceback import FrameSummary, TracebackException self.toolbars.playback.stop() 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 if external_args: self.external_args = shlex.split(external_args) try: argv_orig = sys.argv sys.argv = [script_path.name] + self.external_args except AttributeError: pass try: # pylint: disable=exec-used exec(self.script_path.read_text(encoding='utf-8'), {'__file__': sys.argv[0]}) except Exception as e: # pylint: disable=broad-except self.script_exec_failed = True logging.error(e) te = TracebackException.from_exception(e) # remove the first stack frame, which contains our exec() invocation del te.stack[0] # replace <string> with script path only for the first stack frames # in order to keep intact exec() invocations down the stack # that we're not concerned with for i, frame in enumerate(te.stack): if frame.filename == '<string>': te.stack[i] = FrameSummary(str(self.script_path), frame.lineno, frame.name) else: break print(''.join(te.format())) self.handle_script_error( f'''An error occured while evaluating script: \n{str(e)} \nSee console output for details.''') return finally: sys.argv = argv_orig sys.path.pop() self.script_exec_failed = False if len(vs.get_outputs()) == 0: logging.error('Script has no outputs set.') self.handle_script_error('Script has no outputs set.') return if not reloading: self.toolbars.main.rescan_outputs() for toolbar in self.toolbars: toolbar.on_script_loaded() self.switch_output(self.OUTPUT_INDEX) self.load_storage() else: self.load_storage() for toolbar in self.toolbars: toolbar.on_script_loaded() def load_storage(self) -> None: import yaml vsp_dir = self.script_path.parent / self.VSP_DIR_NAME storage_path = vsp_dir / (self.script_path.stem + '.yml') if not storage_path.exists(): storage_path = self.script_path.with_suffix('.yml') if storage_path.exists(): try: yaml.load(storage_path.open(), Loader=yaml.Loader) except yaml.YAMLError as exc: if isinstance(exc, yaml.MarkedYAMLError): logging.warning( 'Storage parsing failed at line {}:{} ({} {}).' 'Using defaults.'.format(exc.problem_mark.line + 1, exc.problem_mark.column + 1, exc.problem, exc.context)) # pylint: disable=no-member else: logging.warning('Storage parsing failed. Using defaults.') else: logging.info('No storage found. Using defaults.') self.statusbar.label.setText('Ready') def init_outputs(self) -> None: from vspreview.widgets import GraphicsImageItem self.graphics_scene.clear() for output in self.outputs: frame_image = output.render_frame(output.last_showed_frame) raw_frame_item = self.graphics_scene.addPixmap( Qt.QPixmap.fromImage(frame_image)) raw_frame_item.hide() frame_item = GraphicsImageItem(raw_frame_item, frame_image) output.graphics_scene_item = frame_item def reload_script(self) -> None: import gc if not self.script_exec_failed: self.toolbars.misc.save_sync() for toolbar in self.toolbars: toolbar.on_script_unloaded() vs.clear_outputs() self.graphics_scene.clear() self.outputs.clear() # make sure old filter graph is freed gc.collect() self.load_script(self.script_path, reloading=True) self.show_message('Reloaded successfully') def render_frame(self, frame: Frame, output: Optional[Output] = None) -> Qt.QImage: return self.current_output.render_frame(frame) def switch_frame(self, pos: Union[Frame, Time], *, render_frame: bool = True) -> None: if isinstance(pos, Frame): frame = pos time = Time(frame) elif isinstance(pos, Time): frame = Frame(pos) time = pos else: logging.debug('switch_frame(): position is neither Frame nor Time') return if frame > self.current_output.end_frame: return self.current_output.last_showed_frame = frame self.timeline.set_position(frame) self.toolbars.main.on_current_frame_changed(frame, time) for toolbar in self.toolbars: toolbar.on_current_frame_changed(frame, time) if render_frame: self.current_output.graphics_scene_item.setImage( self.render_frame(frame)) def switch_output(self, value: Union[int, Output]) -> None: if len(self.outputs) == 0: return if isinstance(value, Output): index = self.outputs.index_of(value) else: index = value 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.timeline.set_end_frame(self.current_output.end_frame) if self.current_output.frame_to_show is not None: self.current_frame = self.current_output.frame_to_show else: self.current_frame = self.current_output.last_showed_frame for output in self.outputs: output.graphics_scene_item.hide() self.current_output.graphics_scene_item.show() self.graphics_scene.setSceneRect( Qt.QRectF(self.current_output.graphics_scene_item.pixmap().rect())) self.timeline.update_notches() for toolbar in self.toolbars: toolbar.on_current_output_changed(index, prev_index) self.update_statusbar_output_info() @property # type: ignore def current_output(self) -> Output: # type: ignore output = cast(Output, self.toolbars.main.outputs_combobox.currentData()) return output @current_output.setter def current_output(self, value: Output) -> None: self.switch_output(self.outputs.index_of(value)) @property # type: ignore def current_frame(self) -> Frame: # type: ignore return self.current_output.last_showed_frame @current_frame.setter def current_frame(self, value: Frame) -> None: self.switch_frame(value) @property # type: ignore def current_time(self) -> Time: # type: ignore return Time(self.current_output.last_showed_frame) @current_time.setter def current_time(self, value: Time) -> None: self.switch_frame(value) @property def outputs(self) -> Outputs: # type: ignore return cast(Outputs, self.toolbars.main.outputs) def handle_script_error(self, message: str) -> None: # logging.error(message) self.script_error_dialog.label.setText(message) self.script_error_dialog.open() def on_wheel_scrolled(self, steps: int) -> None: new_index = self.toolbars.main.zoom_combobox.currentIndex() + steps if new_index < 0: new_index = 0 elif new_index >= len(self.toolbars.main.zoom_levels): new_index = len(self.toolbars.main.zoom_levels) - 1 self.toolbars.main.zoom_combobox.setCurrentIndex(new_index) def show_message(self, message: str, timeout: Optional[int] = None) -> None: if timeout is None: timeout = self.STATUSBAR_MESSAGE_TIMEOUT self.statusbar.showMessage(message, timeout) def update_statusbar_output_info(self, output: Optional[Output] = None) -> None: if output is None: output = self.current_output self.statusbar.total_frames_label.setText('{} frames'.format( output.total_frames)) self.statusbar.duration_label.setText( # Display duration without -1 offset to match other video tools '{}'.format(TimeInterval(self.current_output.total_frames))) self.statusbar.resolution_label.setText('{}x{}'.format( output.width, output.height)) if not output.has_alpha: self.statusbar.pixel_format_label.setText('{}'.format( output.format.name)) else: self.statusbar.pixel_format_label.setText( 'Clip: {}, Alpha: {}'.format(output.format.name, output.format_alpha.name)) if output.fps_den != 0: self.statusbar.fps_label.setText('{}/{} = {:.3f} fps'.format( output.fps_num, output.fps_den, output.fps_num / output.fps_den)) else: self.statusbar.fps_label.setText('{}/{} fps'.format( output.fps_num, output.fps_den)) def event(self, event: Qt.QEvent) -> bool: if event.type() == Qt.QEvent.LayoutRequest: self.timeline.full_repaint() return super().event(event) # misc methods def showEvent(self, event: Qt.QShowEvent) -> None: super().showEvent(event) self.graphics_view.setSizePolicy( Qt.QSizePolicy(Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Expanding)) def closeEvent(self, event: Qt.QCloseEvent) -> None: if self.save_on_exit: self.toolbars.misc.save() def __getstate__(self) -> Mapping[str, Any]: state = { attr_name: getattr(self, attr_name) for attr_name in self.storable_attrs } state.update({ 'timeline_mode': self.timeline.mode, 'window_geometry': bytes(self.saveGeometry()), 'window_state': bytes(self.saveState()), }) return state def __setstate__(self, state: Mapping[str, Any]) -> None: # toolbars is singleton, # so it initialize itself right in its __setstate__() try: timeline_mode = state['timeline_mode'] if not Timeline.Mode.is_valid(timeline_mode): raise TypeError except (KeyError, TypeError): logging.warning('Storage loading: failed to parse timeline mode.' ' Using default.') timeline_mode = self.TIMELINE_MODE self.timeline.mode = timeline_mode try: window_geometry = state['window_geometry'] if not isinstance(window_geometry, bytes): raise TypeError self.restoreGeometry(window_geometry) except (KeyError, TypeError): logging.warning('Storage loading: failed to parse window geometry.' ' Using default.') try: window_state = state['window_state'] if not isinstance(window_state, bytes): raise TypeError self.restoreState(window_state) except (KeyError, TypeError): logging.warning('Storage loading: failed to parse window state.' ' Using default.')
class MainWindow(AbstractMainWindow): # those are defaults that can be overriden at runtime or used as fallbacks AUTOSAVE_ENABLED = True AUTOSAVE_INTERVAL = 30 * 1000 # s BASE_PPI = 96 # PPI BENCHMARK_CLEAR_CACHE = False BENCHMARK_REFRESH_INTERVAL = 150 # ms DARK_THEME = True FPS_AVERAGING_WINDOW_SIZE = FrameInterval(100) FPS_REFRESH_INTERVAL = 150 # ms LOG_LEVEL = logging.DEBUG OPENGL_RENDERING = False OUTPUT_INDEX = 0 PLAY_BUFFER_SIZE = FrameInterval(get_usable_cpus_count()) PNG_COMPRESSION_LEVEL = 0 # 0 - 100 SAVE_TEMPLATE = '{script_name}_{frame}' SEEK_STEP = 1 # frames STATUSBAR_MESSAGE_TIMEOUT = 3 * 1000 # s # it's allowed to stretch target interval betweewn notches by N% at most TIMELINE_LABEL_NOTCHES_MARGIN = 20 # % TIMELINE_MODE = 'frame' # would be used for formats with subsampling VS_OUTPUT_RESIZER = Output.Resizer.Bicubic VS_OUTPUT_MATRIX = Output.Matrix.BT709 VS_OUTPUT_TRANSFER = Output.Transfer.BT709 VS_OUTPUT_PRIMARIES = Output.Primaries.BT709 VS_OUTPUT_RANGE = Output.Range.LIMITED VS_OUTPUT_CHROMALOC = Output.ChromaLoc.LEFT VS_OUTPUT_PREFER_PROPS = True VS_OUTPUT_RESIZER_KWARGS = {} # type: Mapping[str, str] BENCHMARK_FRAME_DATA_SHARING_FIX = True DEBUG_PLAY_FPS = False DEBUG_TOOLBAR = False DEBUG_TOOLBAR_BUTTONS_PRINT_STATE = False yaml_tag = '!MainWindow' storable_attrs = [ 'toolbars', ] __slots__ = storable_attrs + [ 'app', 'display_scale', 'clipboard', 'script_path', 'save_on_exit', 'timeline', 'main_layout', 'graphics_scene', 'graphics_view', 'script_error_dialog', 'central_widget', 'statusbar', 'opengl_widget', ] def __init__(self) -> None: from qdarkstyle import load_stylesheet_pyqt5 super().__init__() # logging logging.basicConfig(format='{asctime}: {levelname}: {message}', style='{', level=self.LOG_LEVEL) logging.Formatter.default_msec_format = '%s.%03d' # ??? self.app = Qt.QApplication.instance() if self.DARK_THEME: self.app.setStyleSheet( self.patch_dark_stylesheet(load_stylesheet_pyqt5())) self.ensurePolished() self.display_scale = self.app.primaryScreen().logicalDotsPerInch( ) / self.BASE_PPI self.setWindowTitle('VSPreview') self.move(400, 0) self.setup_ui() # global self.clipboard = self.app.clipboard() self.script_path = Path() self.save_on_exit = True # graphics view self.graphics_scene = Qt.QGraphicsScene(self) self.graphics_view.setScene(self.graphics_scene) self.opengl_widget = None if self.OPENGL_RENDERING: self.opengl_widget = Qt.QOpenGLWidget() self.graphics_view.setViewport(self.opengl_widget) self.graphics_view.wheelScrolled.connect(self.on_wheel_scrolled) # timeline self.timeline.clicked.connect(self.switch_frame) # init toolbars and outputs self.toolbars = Toolbars(self) self.main_layout.addWidget(self.toolbars.main) for toolbar in self.toolbars: self.main_layout.addWidget(toolbar) self.toolbars.main.layout().addWidget(toolbar.toggle_button) set_qobject_names(self) self.setObjectName('MainWindow') def setup_ui(self) -> None: from vspreview.widgets import GraphicsView # mainWindow.resize(1300, 808) self.central_widget = Qt.QWidget(self) self.main_layout = Qt.QVBoxLayout(self.central_widget) self.setCentralWidget(self.central_widget) self.graphics_view = GraphicsView(self.central_widget) self.graphics_view.setBackgroundBrush(self.palette().brush( Qt.QPalette.Window)) self.graphics_view.setSizePolicy(Qt.QSizePolicy.Fixed, Qt.QSizePolicy.Fixed) self.graphics_view.setDragMode(Qt.QGraphicsView.ScrollHandDrag) self.graphics_view.setTransformationAnchor( GraphicsView.AnchorUnderMouse) self.main_layout.addWidget(self.graphics_view) self.timeline = Timeline(self.central_widget) self.main_layout.addWidget(self.timeline) # status bar self.statusbar = StatusBar(self.central_widget) self.statusbar.total_frames_label = Qt.QLabel(self.central_widget) self.statusbar.total_frames_label.setObjectName( 'MainWindow.statusbar.total_frames_label') self.statusbar.addWidget(self.statusbar.total_frames_label) self.statusbar.duration_label = Qt.QLabel(self.central_widget) self.statusbar.duration_label.setObjectName( 'MainWindow.statusbar.duration_label') self.statusbar.addWidget(self.statusbar.duration_label) self.statusbar.resolution_label = Qt.QLabel(self.central_widget) self.statusbar.resolution_label.setObjectName( 'MainWindow.statusbar.resolution_label') self.statusbar.addWidget(self.statusbar.resolution_label) self.statusbar.pixel_format_label = Qt.QLabel(self.central_widget) self.statusbar.pixel_format_label.setObjectName( 'MainWindow.statusbar.pixel_format_label') self.statusbar.addWidget(self.statusbar.pixel_format_label) self.statusbar.fps_label = Qt.QLabel(self.central_widget) self.statusbar.fps_label.setObjectName( 'MainWindow.statusbar.fps_label') self.statusbar.addWidget(self.statusbar.fps_label) self.statusbar.label = Qt.QLabel(self.central_widget) self.statusbar.label.setObjectName('MainWindow.statusbar.label') self.statusbar.addPermanentWidget(self.statusbar.label) self.setStatusBar(self.statusbar) # dialogs self.script_error_dialog = ScriptErrorDialog(self) def patch_dark_stylesheet(self, stylesheet: str) -> str: return stylesheet + 'QGraphicsView { border: 0px; padding: 0px; }' def load_script(self, script_path: Path, external_args: str = '') -> None: from traceback import print_exc import shlex self.toolbars.playback.stop() 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 if external_args: self.external_args = shlex.split(external_args) try: argv_orig = sys.argv sys.argv = [sys.argv[1]] + self.external_args except AttributeError: pass try: exec(self.script_path.read_text(), {}) # pylint: disable=exec-used except Exception: # pylint: disable=broad-except logging.error( 'Script contains error(s). Check following lines for details.') self.handle_script_error( 'Script contains error(s). See console output for details.') print_exc() return finally: sys.argv = argv_orig sys.path.pop() if len(vs.get_outputs()) == 0: logging.error('Script has no outputs set.') self.handle_script_error('Script has no outputs set.') return self.toolbars.main.rescan_outputs() # self.init_outputs() self.switch_output(self.OUTPUT_INDEX) self.load_storage() def load_storage(self) -> None: import yaml storage_path = self.script_path.with_suffix('.yml') if storage_path.exists(): try: yaml.load(storage_path.open(), Loader=yaml.Loader) except yaml.YAMLError as exc: if isinstance(exc, yaml.MarkedYAMLError): logging.warning( 'Storage parsing failed on line {} column {}. Using defaults.' .format(exc.problem_mark.line + 1, exc.problem_mark.column + 1)) # pylint: disable=no-member else: logging.warning('Storage parsing failed. Using defaults.') else: logging.info('No storage found. Using defaults.') self.statusbar.label.setText('Ready') def init_outputs(self) -> None: self.graphics_scene.clear() for output in self.outputs: frame_pixmap = output.render_frame(output.last_showed_frame) frame_item = self.graphics_scene.addPixmap(frame_pixmap) frame_item.hide() output.graphics_scene_item = frame_item def reload_script(self) -> None: if self.toolbars.misc.autosave_enabled: self.toolbars.misc.save() vs.clear_outputs() self.graphics_scene.clear() self.load_script(self.script_path) self.show_message('Reloaded successfully') def render_frame(self, frame: Frame, output: Optional[Output] = None) -> Qt.QPixmap: return self.current_output.render_frame(frame) def switch_frame(self, frame: Optional[Frame] = None, time: Optional[Time] = None, *, render_frame: bool = True) -> None: if frame is not None: time = Time(frame) elif time is not None: frame = Frame(time) else: logging.debug('switch_frame(): both frame and time is None') return if frame > self.current_output.end_frame: return self.current_output.last_showed_frame = frame self.timeline.set_position(frame) self.toolbars.main.on_current_frame_changed(frame, time) for toolbar in self.toolbars: toolbar.on_current_frame_changed(frame, time) if render_frame: self.current_output.graphics_scene_item.setPixmap( self.render_frame(frame)) def switch_output(self, value: Union[int, Output]) -> None: if len(self.outputs) == 0: return if isinstance(value, Output): index = self.outputs.index_of(value) else: index = value 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.timeline.set_end_frame(self.current_output.end_frame) if self.current_output.frame_to_show is not None: self.current_frame = self.current_output.frame_to_show else: self.current_frame = self.current_output.last_showed_frame for output in self.outputs: output.graphics_scene_item.hide() self.current_output.graphics_scene_item.show() self.graphics_scene.setSceneRect( Qt.QRectF(self.current_output.graphics_scene_item.pixmap().rect())) self.timeline.update_notches() for toolbar in self.toolbars: toolbar.on_current_output_changed(index, prev_index) self.update_statusbar_output_info() @property # type: ignore def current_output(self) -> Output: # type: ignore output = cast(Output, self.toolbars.main.outputs_combobox.currentData()) return output @current_output.setter def current_output(self, value: Output) -> None: self.switch_output(self.outputs.index_of(value)) @property # type: ignore def current_frame(self) -> Frame: # type: ignore return self.current_output.last_showed_frame @current_frame.setter def current_frame(self, value: Frame) -> None: self.switch_frame(value) @property # type: ignore def current_time(self) -> Time: # type: ignore return Time(self.current_output.last_showed_frame) @current_time.setter def current_time(self, value: Time) -> None: self.switch_frame(time=value) @property def outputs(self) -> Outputs: # type: ignore return cast(Outputs, self.toolbars.main.outputs) def handle_script_error(self, message: str) -> None: # logging.error(message) self.script_error_dialog.label.setText(message) self.script_error_dialog.open() def on_wheel_scrolled(self, steps: int) -> None: new_index = self.toolbars.main.zoom_combobox.currentIndex() + steps if new_index < 0: new_index = 0 elif new_index >= len(self.toolbars.main.zoom_levels): new_index = len(self.toolbars.main.zoom_levels) - 1 self.toolbars.main.zoom_combobox.setCurrentIndex(new_index) def show_message(self, message: str, timeout: Optional[int] = None) -> None: if timeout is None: timeout = self.STATUSBAR_MESSAGE_TIMEOUT self.statusbar.showMessage(message, timeout) def update_statusbar_output_info(self, output: Optional[Output] = None) -> None: if output is None: output = self.current_output self.statusbar.total_frames_label.setText('{} frames '.format( output.total_frames)) self.statusbar.duration_label.setText('{} '.format(output.total_time)) self.statusbar.resolution_label.setText('{}x{} '.format( output.width, output.height)) if not output.has_alpha: self.statusbar.pixel_format_label.setText('{} '.format( output.format.name)) else: self.statusbar.pixel_format_label.setText( 'Clip: {}, Alpha: {} '.format(output.format.name, output.format_alpha.name)) if output.fps_den != 0: self.statusbar.fps_label.setText('{}/{} = {:.3f} fps '.format( output.fps_num, output.fps_den, output.fps_num / output.fps_den)) else: self.statusbar.fps_label.setText('{}/{} fps '.format( output.fps_num, output.fps_den)) def event(self, event: Qt.QEvent) -> bool: if event.type() == Qt.QEvent.LayoutRequest: self.timeline.full_repaint() return super().event(event) # misc methods def showEvent(self, event: Qt.QShowEvent) -> None: super().showEvent(event) self.graphics_view.setSizePolicy( Qt.QSizePolicy(Qt.QSizePolicy.Expanding, Qt.QSizePolicy.Expanding)) def closeEvent(self, event: Qt.QCloseEvent) -> None: if (self.toolbars.misc.autosave_enabled and self.save_on_exit): self.toolbars.misc.save() def __getstate__(self) -> Mapping[str, Any]: state = { attr_name: getattr(self, attr_name) for attr_name in self.storable_attrs } state.update({'timeline_mode': self.timeline.mode}) return state def __setstate__(self, state: Mapping[str, Any]) -> None: # toolbars is singleton, # so it initialize itself right in its __setstate__() try: timeline_mode = state['timeline_mode'] if not Timeline.Mode.is_valid(timeline_mode): raise TypeError except (KeyError, TypeError): logging.warning( 'Storage loading: failed to parse timeline mode. Using default.' ) timeline_mode = self.TIMELINE_MODE self.timeline.mode = timeline_mode