class RightPanel(FFrame): def __init__(self, app, parent=None): super().__init__(parent) self._app = app self.widget = None self._layout = QHBoxLayout(self) self.setLayout(self._layout) self.setObjectName('right_panel') self.set_theme_style() self.setup_ui() def set_theme_style(self): style_str = ''' #{0} {{ background: transparent; }} '''.format(self.objectName()) self.setStyleSheet(style_str) def set_widget(self, widget): if self.widget and self.widget != widget: self._layout.replaceWidget(self.widget, widget) else: self._layout.addWidget(widget) self.widget = widget def setup_ui(self): self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0)
class MainWidget(QWidget): def __init__(self): super().__init__() self.hex_data_manager = HexDataManager("") self.current_table_widget: TableWidget = TableWidget( self.hex_data_manager) self.controller_widget = ControllerWidget(self.current_table_widget, self) self.box_l = QHBoxLayout() self.box_l.addWidget(self.current_table_widget) self.box_l.addWidget(self.controller_widget) self.setLayout(self.box_l) def open_file(self): file_name, _ = QFileDialog.getOpenFileName() if os.path.isfile(file_name): self.hex_data_manager = HexDataManager(file_name) table_widget = TableWidget(self.hex_data_manager) self.current_table_widget.close() self.box_l.replaceWidget(self.current_table_widget, table_widget) self.current_table_widget = table_widget self.controller_widget.set_table_widget(table_widget) self.controller_widget.update_current_page_label() def save_file(self): file_name, _ = QFileDialog.getSaveFileName() if file_name == "": return self.hex_data_manager.write_changes_in_file(file_name) def new_file(self): self.hex_data_manager = HexDataManager("") table_widget = TableWidget(self.hex_data_manager) self.current_table_widget.close() self.box_l.replaceWidget(self.current_table_widget, table_widget) self.current_table_widget = table_widget self.controller_widget.set_table_widget(table_widget) self.controller_widget.update_current_page_label() def keyPressEvent(self, a0: QKeyEvent) -> None: if a0.key() == 16777220: self.current_table_widget.disable_edit_mode() else: self.current_table_widget.handle_representation_symbol(a0.text()) def wheelEvent(self, a0: QWheelEvent) -> None: if a0.angleDelta().y() > 0: self.controller_widget.get_right() else: self.controller_widget.get_left()
class FileDialog(QWidget): def __init__(self, parent: StatisticsGUI = None): super().__init__() self.parent = parent self.setWindowIcon(QIcon('../images/Logo.png')) layout = QVBoxLayout() # first line: select type of comparison, inter-dataset or intra-dataset self.layout_horizontal1 = QHBoxLayout() self.comp_type_label = QLabel("Select comparison type: ") self.comp_type = QComboBox() self.comp_type.addItem("Inter-Dataset Comparison") self.comp_type.addItem("Intra-Dataset Comparison") self.comp_type_crt_text = self.comp_type.currentText() self.comp_type.currentTextChanged.connect(self.comp_type_changed) self.layout_horizontal1.addWidget(self.comp_type_label, 1) self.layout_horizontal1.addWidget(self.comp_type, 2, alignment=Qt.AlignLeft) self.layout_horizontal1.addStretch(3) layout.addLayout(self.layout_horizontal1, 2) layout.addStretch(1) # second line: check datasets to compare (inter) / dataset to compare classes (intra) self.layout_vertical2_inter = QVBoxLayout() self.check_ds = QLabel('Check datasets to compare: ') self.layout_horizontal2 = QHBoxLayout() self.check_boxes = DataSetsManager.get_data_set_to_compare(self.layout_horizontal2) self.layout_vertical2_inter.addWidget(self.check_ds, 1, alignment=Qt.AlignLeft) self.layout_vertical2_inter.addLayout(self.layout_horizontal2, 2) self.wid_1_inter = QWidget() self.wid_1_inter.setLayout(self.layout_vertical2_inter) layout.addWidget(self.wid_1_inter, 2) layout.addStretch(1) # for intra self.layout_vertical2_intra = QHBoxLayout() self.select_ds = QLabel("Select dataset: ") self.list_ds = DataSetsManager.get_data_set_combo() self.list_ds.setCurrentIndex(0) self.intra_ds_current = self.list_ds.itemText(0) self.list_ds.currentTextChanged.connect(self.dataset_changed) self.layout_vertical2_intra.addWidget(self.select_ds, 1) self.layout_vertical2_intra.addWidget(self.list_ds, 2, alignment=Qt.AlignLeft) self.layout_vertical2_intra.addStretch(3) self.wid_1_intra = QWidget() self.wid_1_intra.setLayout(self.layout_vertical2_intra) # third line: select metric for comparison self.layout_hor3_inter = QHBoxLayout() self.select_metric_label = QLabel("Select metric: ") self.select_metric_inter = QComboBox() self.dict_metric_inter = {"Number of detections": ("detections_count", 'd'), "Number of detections (unique per box)": ("unique_truth_count", 'd'), "Precision": ("precision", 'f'), "Recall": ("recall", 'f'), "F1 score": ("F1-score", 'f'), "True Positives": ("TP", 'd'), "False Positives": ("FP", 'd'), "False Negatives": ("FN", 'd'), "Average IoU": ("average_IoU", 'p'), "mean Average Precision": ("mAP", 'p'), "Detection time": ("Time_seconds", 'd') } for metric in self.dict_metric_inter: self.select_metric_inter.addItem(metric) self.layout_hor3_inter.addWidget(self.select_metric_label, 1) self.layout_hor3_inter.addWidget(self.select_metric_inter, 2, alignment=Qt.AlignLeft) self.layout_hor3_inter.addStretch(3) self.wid_2_inter = QWidget() self.wid_2_inter.setLayout(self.layout_hor3_inter) layout.addWidget(self.wid_2_inter, 2) # for intra self.layout_hor3_intra = QHBoxLayout() self.select_metric_intra = QComboBox() self.dict_metric_intra = {"Average precision": ("ap", 'p'), "Number of detections (unique per box)": ("TC", 'd'), "Precision": ("Precision", 'f'), "Recall": ("Recall", 'f'), "F1 score": ("F1-score", 'f'), "True Positives": ("TP", 'd'), "False Positives": ("FP", 'd'), "False Negatives": ("FN", 'd'), "Average IoU": ("Avg_IOU", 'p') } for metric in self.dict_metric_intra: self.select_metric_intra.addItem(metric) self.select_metric_label_intra = QLabel("Select metric: ") self.layout_hor3_intra.addWidget(self.select_metric_label_intra, 1) self.layout_hor3_intra.addWidget(self.select_metric_intra, 2, alignment=Qt.AlignLeft) self.layout_hor3_intra.addStretch(3) self.wid_2_intra = QWidget() self.wid_2_intra.setLayout(self.layout_hor3_intra) # fourth line: select chart type self.layout_hor4 = QHBoxLayout() self.select_chart = QComboBox() self.select_chart.addItem("Horizontal Bar chart") self.select_chart.addItem("Vertical Bar chart") self.select_chart.addItem("Pie chart") self.select_chart_label = QLabel("Select chart type: ") self.layout_hor4.addWidget(self.select_chart_label, 1) self.layout_hor4.addWidget(self.select_chart, 2, alignment=Qt.AlignLeft) self.layout_hor4.addStretch(3) layout.addLayout(self.layout_hor4, 1) # fifth line: chart self.graph_layout = QHBoxLayout() self.crt_chart = QWidget() self.graph_layout.addWidget(self.crt_chart) layout.addLayout(self.graph_layout, 10) # sixth line: Run and Back self.back_button = QPushButton("Back") self.run_button = QPushButton("Run") self.hor_box = QHBoxLayout() self.hor_box.addStretch(9) self.hor_box.addWidget(self.back_button, 1) self.hor_box.addWidget(self.run_button, 1) self.back_button.clicked.connect(self.back_statistics) self.run_button.clicked.connect(self.run_statistics) layout.addLayout(self.hor_box, 1) self.wid_1_inter.setObjectName("StatisticsLayout") self.wid_1_intra.setObjectName("StatisticsLayout") self.wid_2_inter.setObjectName("StatisticsLayout") self.wid_2_intra.setObjectName("StatisticsLayout") self.run_statistics() self.setLayout(layout) def comp_type_changed(self, text): if text != self.comp_type_crt_text: self.comp_type_crt_text = text if text[:5] == "Inter": self.wid_1_intra.hide() self.wid_2_intra.hide() self.wid_1_inter.show() self.wid_2_inter.show() self.layout().replaceWidget(self.wid_1_intra, self.wid_1_inter) self.layout().replaceWidget(self.wid_2_intra, self.wid_2_inter) elif text[:5] == "Intra": self.wid_1_inter.hide() self.wid_2_inter.hide() self.wid_1_intra.show() self.wid_2_intra.show() self.layout().replaceWidget(self.wid_1_inter, self.wid_1_intra) self.layout().replaceWidget(self.wid_2_inter, self.wid_2_intra) def dataset_changed(self, text): self.intra_ds_current = text def back_statistics(self): self.parent.back_to_parent() def run_statistics(self): text = self.comp_type_crt_text x_vals = [] y_vals = [] if text[:5] == "Inter": data_sets = [] for check_box in self.check_boxes: if check_box.isChecked(): data_sets.append(self.check_boxes[check_box]) metric = self.select_metric_inter.currentText() metric_alias = self.dict_metric_inter[metric] res = ResultsManager.get_results_inter(metric_alias, data_sets) # self.graphWidget.plot(data_sets, res) x_vals = data_sets y_vals = res elif text[:5] == "Intra": ds_name = self.intra_ds_current metric = self.select_metric_intra.currentText() metric_alias = self.dict_metric_intra[metric] names, res = ResultsManager.get_results_intra(metric_alias, ds_name) # self.graphWidget.plot(names, res) x_vals = names y_vals = res #horizontal bar chart_view = None if self.select_chart.currentIndex() == 0: chart = QChart(flags=Qt.WindowFlags()) series = QHorizontalBarSeries() for i in range(len(x_vals)): name = x_vals[i] set0 = QBarSet(name) set0.append(y_vals[i]) series.append(set0) chart.addSeries(series) if text[:5] == "Inter": chart.setTitle(f"Comparison by {self.select_metric_inter.currentText()}") else: chart.setTitle(f"Comparison by {self.select_metric_intra.currentText()}") chart.setAnimationOptions(QChart.SeriesAnimations) chart_view = QChartView(chart) self.graph_layout.replaceWidget(self.crt_chart, chart_view) self.crt_chart = chart_view axisX = QValueAxis() chart.addAxis(axisX, Qt.AlignBottom) series.attachAxis(axisX) axisX.applyNiceNumbers() chart.legend().setVisible(True) chart.legend().setAlignment(Qt.AlignBottom) chart_view.setRenderHint(QPainter.Antialiasing) chart_view.setBackgroundBrush(QColor(0, 0, 0, 255)) chart.setBackgroundBrush(QColor(255, 255, 0, 255)) # vertical bar elif self.select_chart.currentIndex() == 1: chart = QChart(flags=Qt.WindowFlags()) series = QBarSeries() for i in range(len(x_vals)): name = x_vals[i] set0 = QBarSet(name) set0.append(y_vals[i]) series.append(set0) chart.addSeries(series) if text[:5] == "Inter": chart.setTitle(f"Comparison by {self.select_metric_inter.currentText()}") else: chart.setTitle(f"Comparison by {self.select_metric_intra.currentText()}") chart.setAnimationOptions(QChart.SeriesAnimations) chart_view = QChartView(chart) self.graph_layout.replaceWidget(self.crt_chart, chart_view) self.crt_chart = chart_view axisY = QValueAxis() chart.addAxis(axisY, Qt.AlignLeft) series.attachAxis(axisY) axisY.applyNiceNumbers() chart.legend().setVisible(True) chart.legend().setAlignment(Qt.AlignBottom) chart_view.setRenderHint(QPainter.Antialiasing) chart_view.setBackgroundBrush(QColor(0, 0, 0, 255)) chart.setBackgroundBrush(QColor(255, 255, 0, 255)) # pie chart elif self.select_chart.currentIndex() == 2: chart = QChart(flags=Qt.WindowFlags()) series = QPieSeries() for i in range(len(x_vals)): series.append(x_vals[i], y_vals[i]) chart.addSeries(series) if text[:5] == "Inter": chart.setTitle(f"Comparison by {self.select_metric_inter.currentText()}") else: chart.setTitle(f"Comparison by {self.select_metric_intra.currentText()}") chart.setAnimationOptions(QChart.SeriesAnimations) chart.legend().setAlignment(Qt.AlignBottom) # chart.legend().setFont(QFont("Arial", 12)) chart_view = QChartView(chart) self.graph_layout.replaceWidget(self.crt_chart, chart_view) self.crt_chart = chart_view chart_view.setRenderHint(QPainter.Antialiasing) chart_view.setBackgroundBrush(QColor(0, 0, 0, 255)) chart.setBackgroundBrush(QColor(255, 255, 0, 255)) chart_view.setRubberBand(QChartView.HorizontalRubberBand) chart_view.setRubberBand(QChartView.VerticalRubberBand)
class OptionsSelector(BaseSelector): options = None # can stay not used def __init__(self, *args, base, options=None, add_none=False, on_select=None, **kwargs): super().__init__(*args, base=base, **kwargs) self.add_none = add_none self.on_select = on_select self.options = options or self.options self.option_items = list(options.items()) self.options_selector = None assert self.options is not None, "Please provide either options as argument or subclass" self.layout = QHBoxLayout() self.setLayout(self.layout) self.buildOrReplaceSelector() def buildOrReplaceSelector(self): options_selector = QComboBox() if self.add_none: options_selector.addItem("-----") for key, option in self.option_items: options_selector.addItem(option) options_selector.currentIndexChanged.connect(self.indexChanged) if len(self.option_items) > 0 and not self.add_none: self.selected_option = self.option_items[0][ 0] # We automatically select the first item once rebuilding if not self.options_selector: self.layout.addWidget(options_selector) self.model_selector = options_selector else: self.layout.replaceWidget(self.options_selector, options_selector) self.model_selector.close() self.model_selector.setParent(None) self.model_selector = options_selector self.layout.update() return self.options_selector def indexChanged(self, index): if self.add_none: if index == 0: self.selected_option = None else: self.selected_option = self.option_items[index - 1][ 0] # First item is the key not the label else: self.selected_option = self.option_items[index][0] if self.on_select: self.on_select(self.selected_option) def getValue(self): return self.selected_option
class TableWidget(QWidget): def __init__(self, hex_data_manager): super().__init__() self.is_representation_mode_enabled = False self.representation_pointer = 0 self.current_representation_edit: RepresentationEditWidget = None self.representation_unit_widgets_dict = {} self.hex_unit_widgets_dict = {} self.null_widgets_dict = {} self.verification = HexByteVerificator() self.translator = HexIntTranslator() self.hex_data_manager: HexDataManager = hex_data_manager self.integer_hexer = IntegerHexer() self.lines_per_page = 10 self.bytes_per_page = 16 * self.lines_per_page self.hex_pages = {} self.repr_pages = {} self.current_edit: HexUnitEdit = None self.current_page = 0 self.page_count = 0 self.main_layout = QHBoxLayout() self.setLayout(self.main_layout) self.init_pages() def init_page(self, page_number): representation_page_widget = TablePageWidget(16) representation_widgets_list = [] hex_widgets_list = [] null_widgets = [] self.null_widgets_dict[page_number] = null_widgets self.representation_unit_widgets_dict[ page_number] = representation_widgets_list self.hex_unit_widgets_dict[page_number] = hex_widgets_list hex_page_widget = TablePageWidget(17) page_array = byte_value = self.hex_data_manager.get_page(page_number) self.hex_pages[page_number] = hex_page_widget self.repr_pages[page_number] = representation_page_widget for line in range(self.lines_per_page): self.hex_pages[page_number].add_table_element( QLabel( self.integer_hexer.get_hex_string(self.bytes_per_page * page_number + line * 16))) for c in range(16): byte_position = 16 * line + c if byte_position >= len(page_array): unit_representation =\ RepresentationUnitWidget("", byte_position, self) representation_widgets_list.append(unit_representation) self.repr_pages[page_number].add_table_element( unit_representation) null_widget = NullHexUnitWidget(self, byte_position) self.hex_pages[page_number].add_table_element(null_widget) null_widgets.append(null_widget) continue unit_representation = RepresentationUnitWidget( chr(page_array[byte_position]), byte_position, self) representation_widgets_list.append(unit_representation) self.repr_pages[page_number].add_table_element( unit_representation) hex_unit = HexUnitWidget(page_number, 16 * line + c, page_array, self, unit_representation, self.translator) self.hex_pages[page_number].add_table_element(hex_unit) hex_widgets_list.append(hex_unit) def set_element(self, position_on_page, value_int): h_widget = self.hex_unit_widgets_dict[ self.current_page][position_on_page] (h_widget.set_value(value_int)) (self.representation_unit_widgets_dict[self.current_page] [position_on_page].setText(chr(value_int))) self.hex_data_manager.set_value(self.current_page, position_on_page, value_int) was_edit = value_int != h_widget.init_value h_widget.set_was_edit(was_edit) def init_pages(self): self.page_count = self.hex_data_manager.pages_count self.init_page(0) self.main_layout.addWidget(self.hex_pages[0]) self.main_layout.addWidget(self.repr_pages[0]) def open_null_widgets(self, null_widget): self.disable_representation_edit() null_widgets = self.null_widgets_dict[self.current_page] index = null_widgets.index(null_widget) grid_layout: QGridLayout = self.hex_pages[ self.current_page].new_grid_layout self.hex_data_manager.append_empty_bytes_for_page( self.current_page, index + 1) for i in range(index + 1): hex_unit_widget = HexUnitWidget( self.current_page, null_widgets[i].position_on_page, self.hex_data_manager.get_page(self.current_page), self, self.representation_unit_widgets_dict[self.current_page][ null_widgets[i].position_on_page], self.translator) self.hex_unit_widgets_dict[self.current_page].append( hex_unit_widget) null_widgets[i].close() grid_layout.replaceWidget(null_widgets[i], hex_unit_widget) self.null_widgets_dict[self.current_page] = null_widgets[index + 1:] def set_representation_cursor( self, representation_unit_widget: RepresentationUnitWidget): self.disable_representation_edit() grid_layout: QGridLayout = self.repr_pages[ self.current_page].new_grid_layout self.current_representation_edit = RepresentationEditWidget( representation_unit_widget) self.is_representation_mode_enabled = True self.representation_pointer = ( representation_unit_widget.position_on_page) representation_unit_widget.hide() grid_layout.replaceWidget(representation_unit_widget, self.current_representation_edit) def handle_representation_symbol(self, key_text: str): if len(key_text) == 0: return if not key_text.isascii(): return if self.current_representation_edit is not None: pointer = (self.current_representation_edit. representation_unit_widget.position_on_page) + 1 if pointer > len(self.hex_unit_widgets_dict[self.current_page]): return self.disable_representation_edit() self.set_element(pointer - 1, ord(key_text)) if pointer > self.bytes_per_page - 1: return unit_widget = self.representation_unit_widgets_dict[ self.current_page][pointer] if pointer > len( self.hex_unit_widgets_dict[self.current_page]) - 1: return self.set_representation_cursor(unit_widget) def disable_representation_edit(self): if self.current_representation_edit is None: return grid_layout: QGridLayout = self.repr_pages[ self.current_page].new_grid_layout rep_widget = ( self.current_representation_edit.representation_unit_widget) grid_layout.replaceWidget(self.current_representation_edit, rep_widget) rep_widget.show() self.current_representation_edit.close() self.current_representation_edit = None self.representation_pointer = 0 self.is_representation_mode_enabled = False def new_page(self): if len(self.null_widgets_dict[self.current_page]) > 0: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("Для того, чтобы создать новую страницу, " "необходимо избавиться от NULL на этой." "\nНажмите на последний NULL. И все NULL перед ним " "превратятся в действующие элементы.") msg.setWindowTitle("Не получилось!") msg.exec_() return self.page_count += 1 self.init_page(self.current_page + 1) self.page_set(self.current_page + 1) def disable_edit_mode(self): self.disable_representation_edit() if self.current_edit is not None: self.current_edit.close() new_value = self.current_edit.text() if self.verification.verify_hex_byte(new_value): new_value_hex = new_value.upper() new_value_int = self.translator.get_int(new_value_hex) else: new_value_int = self.current_edit.hex_unit_widget.value was_edit = (new_value_int != self.current_edit.hex_unit_widget.init_value) self.current_edit.hex_unit_widget.set_was_edit(was_edit) representation: QLabel = ( self.current_edit.hex_unit_widget.representation) representation.setText(chr(new_value_int)) self.hex_data_manager.set_value( self.current_edit.hex_unit_widget.page_on, self.current_edit.hex_unit_widget.position_on_page, new_value_int) self.hex_pages[self.current_page].new_grid_layout.replaceWidget( self.current_edit, self.current_edit.hex_unit_widget) self.current_edit.hex_unit_widget.set_value(new_value_int) self.current_edit.hex_unit_widget.show() def make_edit(self, hex_unit_widget: HexUnitWidget): self.disable_edit_mode() qline = HexUnitEdit(hex_unit_widget) hex_unit_widget.hide() self.hex_pages[self.current_page].new_grid_layout.replaceWidget( hex_unit_widget, qline) self.current_edit = qline qline.setFocus() qline.selectAll() def page_set(self, number): self.disable_edit_mode() if number not in range(0, self.page_count): return if number not in self.hex_pages: self.init_page(number) self.hex_pages[self.current_page].hide() self.repr_pages[self.current_page].hide() self.main_layout.replaceWidget( self.hex_pages[self.current_page], self.hex_pages[number], ) self.main_layout.replaceWidget( self.repr_pages[self.current_page], self.repr_pages[number], ) self.current_page = number self.hex_pages[self.current_page].show() self.repr_pages[self.current_page].show()
class RepresentationSelectorDialog(BaseMixin, QDialog): def __init__(self, *args, layer=None, **kwargs): super().__init__(*args, **kwargs) self.representations = QueryList( """ query { myrepresentations(ordering: "-time") { id name store tags } } """, Representation).run() self.sidebar = self.buildSidebar() self.list = self.buildList() mainLayout = QVBoxLayout(self) self.main_widget = QWidget() # Central Widget self.layout = QHBoxLayout() self.layout.addWidget(self.sidebar) self.layout.setStretch(0, 30) self.layout.addWidget(self.list) self.layout.setStretch(1, 300) self.main_widget.setLayout(self.layout) buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttonBox.accepted.connect(self.create) buttonBox.rejected.connect(self.reject) mainLayout.addWidget(self.main_widget) mainLayout.addWidget(buttonBox) self.setMinimumHeight(600) self.setMinimumWidth(1000) self.setLayout(mainLayout) def onPressFilter(self, *args): values = { "sample": self.sampleSelector.getValue(), "experiment": self.experimentSelector.getValue(), "tags": self.tagsField.getValue(), "ordering": self.orderingSelector.getValue() } print(values) self.representations = QueryList( """ query FilteredMyRepresentations($tags: String, $ordering: String, $sample: ID, $experiment: ID ){ myrepresentations(tags: $tags, ordering:$ordering, sample: $sample, experiment: $experiment) { id name store tags } } """, Representation).run(variables=values) newlist = self.buildList() self.layout.replaceWidget(self.list, newlist) self.list.close() self.list.setParent(None) self.list = newlist @asyncSlot() async def create(self): indices = [i.row() for i in self.list.selectedIndexes()] self.selected_reps = [self.representations[i] for i in indices] self.accept() def buildSidebar(self): # Filter self.filterBox = QGroupBox("Filter") self.sampleSelector = SampleSelector(parent=self, base=self.base, with_form=False, add_none=True, on_select=self.onPressFilter) self.experimentSelector = ExperimentSelector( parent=self, base=self.base, with_form=False, add_none=True, on_select=self.onPressFilter) self.orderingSelector = OptionsSelector(parent=self, base=self.base, options={ "-time": "Time (Descending)", "time": "Time (Ascending)" }, on_select=self.onPressFilter) self.tagsField = TextField(initial=" ", parent=self, base=self.base, on_select=self.onPressFilter) layout = QFormLayout() layout.addRow(QLabel("Sample"), self.sampleSelector) layout.addRow(QLabel("Experiment"), self.experimentSelector) layout.addRow(QLabel("Tags"), self.tagsField) layout.addRow(QLabel("Ordering"), self.orderingSelector) self.filterBox.setLayout(layout) # Filter Button self.filterButton = QPushButton("Filter") self.filterButton.clicked.connect(self.onPressFilter) # The Sidebar sidebar = QWidget() sidebarlayout = QVBoxLayout() sidebarlayout.addWidget(self.filterBox) sidebarlayout.addStretch() sidebarlayout.addWidget(self.filterButton) sidebar.setLayout(sidebarlayout) return sidebar def buildList(self): list = QListWidget() list.setSelectionMode(QAbstractItemView.ExtendedSelection) for rep in self.representations: item = QListWidgetItem(list) list.addItem(item) repwidget = RepresentationItemWidget(rep, base=self.base) item.setSizeHint(repwidget.minimumSizeHint()) list.setItemWidget(item, repwidget) return list
class CEditorContent ( QWidget ): def __init__ ( self, editor ): super ().__init__ () self.editor = editor self.toolBarAreaManager = CToolBarAreaManager ( editor ) self.mainLayout = QVBoxLayout () self.mainLayout.setContentsMargins ( 0, 0, 0, 0 ) self.mainLayout.setSpacing ( 0 ) self.contentLayout = QHBoxLayout () self.contentLayout.setContentsMargins ( 0, 0, 0, 0 ) self.contentLayout.setSpacing ( 0 ) self.content = QWidget () self.content.setSizePolicy ( QSizePolicy.Expanding, QSizePolicy.Expanding ) self.editor.signalAdaptiveLayoutChanged.connect ( self.onAdaptiveLayoutChanged ) self.setLayout ( self.mainLayout ) def initialize ( self ): self.toolBarAreaManager.initialize () self.mainLayout.addWidget ( self.toolBarAreaManager.getWidget ( CToolBarAreaManagerArea.Top ) ) self.mainLayout.addLayout ( self.contentLayout ) self.mainLayout.addWidget ( self.toolBarAreaManager.getWidget ( CToolBarAreaManagerArea.Bottom ) ) self.contentLayout.addWidget ( self.toolBarAreaManager.getWidget ( CToolBarAreaManagerArea.Left ) ) self.contentLayout.addWidget ( self.content ) self.contentLayout.addWidget ( self.toolBarAreaManager.getWidget ( CToolBarAreaManagerArea.Right ) ) def getContent ( self ): return self.content def setContent ( self, content ): if isinstance ( content, QWidget ): self.content.setSizePolicy ( QSizePolicy.Expanding, QSizePolicy.Expanding ) self.contentLayout.replaceWidget ( self.content, content ) self.content.setObjectName ( "CEditorContent" ) self.content.deleteLater () self.content = content elif isinstance ( content, QLayout ): contentLayout = content content = QWidget () content.setSizePolicy ( QSizePolicy.Expanding, QSizePolicy.Expanding ) content.setLayout ( contentLayout ) content.setObjectName ( "CEditorContent" ) contentLayout.setContentsMargins ( 0, 0, 0, 0 ) contentLayout.setSpacing ( 0 ) self.contentLayout.replaceWidget ( self.content, content ) self.content.deleteLater () self.content = content def customizeToolBar ( self ): # TODO: CToolBarCustomizeDialog return self.content def toggleToolBarLock ( self ): return self.toolBarAreaManager.toggleLock () def addExpandingSpacer ( self ): return self.toolBarAreaManager.addExpandingSpacer () def addFixedSpacer ( self ): return self.toolBarAreaManager.addFixedSpacer () def getMinimumSizeForOrientation ( self, orientation ) -> QSize: isDefaultOrientation = orientation == self.editor.GetDefaultOrientation () contentMinSize = self.content.layout ().minimumSize () topArea = self.toolBarAreaManager.getWidget ( CToolBarAreaManagerArea.Top ) bottomArea = self.toolBarAreaManager.getWidget ( CToolBarAreaManagerArea.Bottom ) leftArea = self.toolBarAreaManager.getWidget ( CToolBarAreaManagerArea.Left ) rightArea = self.toolBarAreaManager.getWidget ( CToolBarAreaManagerArea.Right ) result = QSize ( 0, 0 ) if isDefaultOrientation: # Take width from left and right areas if we're switching to the editor's default orientation result.setWidth ( result.width () + leftArea.getLargestItemMinimumSize ().width () ) result.setWidth ( result.width () + rightArea.getLargestItemMinimumSize ().width () ) # Use top and bottom area to calculate min height result.setHeight ( result.height () + leftArea.getLargestItemMinimumSize ().height () ) result.setHeight ( result.height () + rightArea.getLargestItemMinimumSize ().height () ) # Add content min size result += contentMinSize # Take the area layout size hints into account. Expand the current result with the toolbar area layout's size hint. # We use size hint rather than minimum size since toolbar area item's size policy is set to preferred. result = result.expandedTo ( QSize ( topArea.layout ().sizeHint ().height (), leftArea.layout ().sizeHint ().width () ) ) result = result.expandedTo ( QSize ( bottomArea.layout ().sizeHint ().height (), rightArea.layout ().sizeHint ().width () ) ) else: # If we're not switching to the default orientation, then we need to use the top and bottom toolbar areas' width # since these areas will be placed at the left and right of the editor content in this case of adaptive layouts result.setWidth ( result.width () + topArea.getLargestItemMinimumSize ().width () ) result.setWidth ( result.width () + bottomArea.getLargestItemMinimumSize ().width () ) # We must also flip where we get toolbar area min height from result.setHeight ( result.height () + leftArea.getLargestItemMinimumSize ().height () ) result.setHeight ( result.height () + rightArea.getLargestItemMinimumSize ().height () ) # Add flipped content min size result += QSize ( contentMinSize.height (), contentMinSize.width () ) result = result.expandedTo ( QSize ( leftArea.layout ().sizeHint ().height (), topArea.layout ().sizeHint ().width () ) ) result = result.expandedTo ( QSize ( rightArea.layout ().sizeHint ().height (), bottomArea.layout ().sizeHint ().width () ) ) return result def onAdaptiveLayoutChanged ( self ): isDefaultOrientation = self.editor.GetOrientation () == self.editor.GetDefaultOrientation () self.mainLayout.setDirection ( QBoxLayout.TopToBottom if isDefaultOrientation else QBoxLayout.LeftToRight ) self.contentLayout.setDirection ( QBoxLayout.LeftToRight if isDefaultOrientation else QBoxLayout.TopToBottom ) def paintEvent ( self, event ): styleOption = QStyleOption () styleOption.initFrom ( self ) painter = QPainter ( self ) self.style ().drawPrimitive ( QStyle.PE_Widget, styleOption, painter, self )
class VidCutter(QWidget): def __init__(self, parent): super(VidCutter, self).__init__(parent) self.parent = parent self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface) self.videoWidget = VideoWidget() self.videoService = VideoService(self) QFontDatabase.addApplicationFont( os.path.join(self.getAppPath(), 'fonts', 'DroidSansMono.ttf')) QFontDatabase.addApplicationFont( os.path.join(self.getAppPath(), 'fonts', 'HelveticaNeue.ttf')) qApp.setFont(QFont('Helvetica Neue', 10)) self.clipTimes = [] self.inCut = False self.movieFilename = '' self.movieLoaded = False self.timeformat = 'hh:mm:ss' self.finalFilename = '' self.totalRuntime = 0 self.initIcons() self.initActions() self.toolbar = QToolBar( floatable=False, movable=False, iconSize=QSize(28, 28), toolButtonStyle=Qt.ToolButtonTextUnderIcon, styleSheet= 'QToolBar QToolButton { min-width:82px; margin-left:10px; margin-right:10px; font-size:14px; }' ) self.initToolbar() self.aboutMenu, self.cliplistMenu = QMenu(), QMenu() self.initMenus() self.seekSlider = VideoSlider(parent=self, sliderMoved=self.setPosition) self.seekSlider.installEventFilter(self) self.initNoVideo() self.cliplist = QListWidget( sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding), contextMenuPolicy=Qt.CustomContextMenu, uniformItemSizes=True, iconSize=QSize(100, 700), dragDropMode=QAbstractItemView.InternalMove, alternatingRowColors=True, customContextMenuRequested=self.itemMenu, styleSheet='QListView::item { margin:10px 5px; }') self.cliplist.setFixedWidth(185) self.cliplist.model().rowsMoved.connect(self.syncClipList) listHeader = QLabel(pixmap=QPixmap( os.path.join(self.getAppPath(), 'images', 'clipindex.png'), 'PNG'), alignment=Qt.AlignCenter) listHeader.setStyleSheet( '''padding:5px; padding-top:8px; border:1px solid #b9b9b9; border-bottom:none; background-color:qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #FFF, stop: 0.5 #EAEAEA, stop: 0.6 #EAEAEA stop:1 #FFF);''' ) self.runtimeLabel = QLabel('<div align="right">00:00:00</div>', textFormat=Qt.RichText) self.runtimeLabel.setStyleSheet( '''font-family:Droid Sans Mono; font-size:10pt; color:#FFF; background:rgb(106, 69, 114) url(:images/runtime.png) no-repeat left center; padding:2px; padding-right:8px; border:1px solid #b9b9b9; border-top:none;''' ) self.clipindexLayout = QVBoxLayout(spacing=0) self.clipindexLayout.setContentsMargins(0, 0, 0, 0) self.clipindexLayout.addWidget(listHeader) self.clipindexLayout.addWidget(self.cliplist) self.clipindexLayout.addWidget(self.runtimeLabel) self.videoLayout = QHBoxLayout() self.videoLayout.setContentsMargins(0, 0, 0, 0) self.videoLayout.addWidget(self.novideoWidget) self.videoLayout.addLayout(self.clipindexLayout) self.timeCounter = QLabel('00:00:00 / 00:00:00', autoFillBackground=True, alignment=Qt.AlignCenter, sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Fixed)) self.timeCounter.setStyleSheet( 'color:#FFF; background:#000; font-family:Droid Sans Mono; font-size:10.5pt; padding:4px;' ) videoplayerLayout = QVBoxLayout(spacing=0) videoplayerLayout.setContentsMargins(0, 0, 0, 0) videoplayerLayout.addWidget(self.videoWidget) videoplayerLayout.addWidget(self.timeCounter) self.videoplayerWidget = QWidget(self, visible=False) self.videoplayerWidget.setLayout(videoplayerLayout) self.menuButton = QPushButton(icon=self.aboutIcon, flat=True, toolTip='About', statusTip='About', iconSize=QSize(24, 24), cursor=Qt.PointingHandCursor) self.menuButton.setMenu(self.aboutMenu) self.muteButton = QPushButton(icon=self.unmuteIcon, flat=True, toolTip='Mute', statusTip='Toggle audio mute', cursor=Qt.PointingHandCursor, clicked=self.muteAudio) self.volumeSlider = QSlider(Qt.Horizontal, toolTip='Volume', statusTip='Adjust volume level', cursor=Qt.PointingHandCursor, value=50, sizePolicy=QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Minimum), minimum=0, maximum=100, sliderMoved=self.setVolume) self.volumeSlider.setStyleSheet( '''QSlider::groove:horizontal { height:40px; } QSlider::sub-page:horizontal { border:1px outset #6A4572; background:#6A4572; margin:2px; } QSlider::handle:horizontal { image: url(:images/knob.png) no-repeat top left; width:20px; }''' ) self.saveAction = QPushButton( self.parent, icon=self.saveIcon, text='Save Video', flat=True, toolTip='Save Video', clicked=self.cutVideo, cursor=Qt.PointingHandCursor, iconSize=QSize(30, 30), statusTip='Save video clips merged as a new video file', enabled=False) self.saveAction.setStyleSheet( '''QPushButton { color:#FFF; padding:8px; font-size:12pt; border:1px inset #481953; border-radius:4px; background-color:rgb(106, 69, 114); } QPushButton:!enabled { background-color:rgba(0, 0, 0, 0.1); color:rgba(0, 0, 0, 0.3); border:1px inset #CDCDCD; } QPushButton:hover { background-color:rgba(255, 255, 255, 0.8); color:#444; } QPushButton:pressed { background-color:rgba(218, 218, 219, 0.8); color:#444; }''' ) controlsLayout = QHBoxLayout() controlsLayout.addStretch(1) controlsLayout.addWidget(self.toolbar) controlsLayout.addSpacerItem(QSpacerItem(20, 1)) controlsLayout.addWidget(self.saveAction) controlsLayout.addStretch(1) controlsLayout.addWidget(self.muteButton) controlsLayout.addWidget(self.volumeSlider) controlsLayout.addSpacing(1) controlsLayout.addWidget(self.menuButton) layout = QVBoxLayout() layout.setContentsMargins(10, 10, 10, 4) layout.addLayout(self.videoLayout) layout.addWidget(self.seekSlider) layout.addLayout(controlsLayout) self.setLayout(layout) self.mediaPlayer.setVideoOutput(self.videoWidget) self.mediaPlayer.stateChanged.connect(self.mediaStateChanged) self.mediaPlayer.positionChanged.connect(self.positionChanged) self.mediaPlayer.durationChanged.connect(self.durationChanged) self.mediaPlayer.error.connect(self.handleError) def initNoVideo(self) -> None: novideoImage = QLabel(alignment=Qt.AlignCenter, autoFillBackground=False, pixmap=QPixmap( os.path.join(self.getAppPath(), 'images', 'novideo.png'), 'PNG'), sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.MinimumExpanding)) novideoImage.setBackgroundRole(QPalette.Dark) novideoImage.setContentsMargins(0, 20, 0, 20) self.novideoLabel = QLabel(alignment=Qt.AlignCenter, autoFillBackground=True, sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Minimum)) self.novideoLabel.setBackgroundRole(QPalette.Dark) self.novideoLabel.setContentsMargins(0, 20, 15, 60) novideoLayout = QVBoxLayout(spacing=0) novideoLayout.addWidget(novideoImage) novideoLayout.addWidget(self.novideoLabel, alignment=Qt.AlignTop) self.novideoMovie = QMovie( os.path.join(self.getAppPath(), 'images', 'novideotext.gif')) self.novideoMovie.frameChanged.connect(self.setNoVideoText) self.novideoMovie.start() self.novideoWidget = QWidget(self, autoFillBackground=True) self.novideoWidget.setBackgroundRole(QPalette.Dark) self.novideoWidget.setLayout(novideoLayout) def initIcons(self) -> None: self.appIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'vidcutter.png')) self.openIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'addmedia.png')) self.playIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'play.png')) self.pauseIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'pause.png')) self.cutStartIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'cut-start.png')) self.cutEndIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'cut-end.png')) self.saveIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'save.png')) self.muteIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'muted.png')) self.unmuteIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'unmuted.png')) self.upIcon = QIcon(os.path.join(self.getAppPath(), 'images', 'up.png')) self.downIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'down.png')) self.removeIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'remove.png')) self.removeAllIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'remove-all.png')) self.successIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'success.png')) self.aboutIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'about.png')) self.completePlayIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'complete-play.png')) self.completeOpenIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'complete-open.png')) self.completeRestartIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'complete-restart.png')) self.completeExitIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'complete-exit.png')) def initActions(self) -> None: self.openAction = QAction(self.openIcon, 'Add Media', self, statusTip='Select media source', triggered=self.openFile) self.playAction = QAction(self.playIcon, 'Play Video', self, statusTip='Play selected media', triggered=self.playVideo, enabled=False) self.cutStartAction = QAction(self.cutStartIcon, 'Set Start', self, toolTip='Set Start', statusTip='Set start marker', triggered=self.cutStart, enabled=False) self.cutEndAction = QAction(self.cutEndIcon, 'Set End', self, statusTip='Set end marker', triggered=self.cutEnd, enabled=False) self.moveItemUpAction = QAction( self.upIcon, 'Move Up', self, statusTip='Move clip position up in list', triggered=self.moveItemUp, enabled=False) self.moveItemDownAction = QAction( self.downIcon, 'Move Down', self, statusTip='Move clip position down in list', triggered=self.moveItemDown, enabled=False) self.removeItemAction = QAction( self.removeIcon, 'Remove clip', self, statusTip='Remove selected clip from list', triggered=self.removeItem, enabled=False) self.removeAllAction = QAction(self.removeAllIcon, 'Clear list', self, statusTip='Clear all clips from list', triggered=self.clearList, enabled=False) self.aboutAction = QAction('About %s' % qApp.applicationName(), self, statusTip='Credits and acknowledgements', triggered=self.aboutInfo) self.aboutQtAction = QAction('About Qt', self, statusTip='About Qt', triggered=qApp.aboutQt) self.mediaInfoAction = QAction( 'Media Information', self, statusTip='Media information from loaded video file', triggered=self.mediaInfo, enabled=False) def initToolbar(self) -> None: self.toolbar.addAction(self.openAction) self.toolbar.addAction(self.playAction) self.toolbar.addSeparator() self.toolbar.addAction(self.cutStartAction) self.toolbar.addAction(self.cutEndAction) self.toolbar.addSeparator() def initMenus(self) -> None: self.aboutMenu.addAction(self.mediaInfoAction) self.aboutMenu.addSeparator() self.aboutMenu.addAction(self.aboutQtAction) self.aboutMenu.addAction(self.aboutAction) self.cliplistMenu.addAction(self.moveItemUpAction) self.cliplistMenu.addAction(self.moveItemDownAction) self.cliplistMenu.addSeparator() self.cliplistMenu.addAction(self.removeItemAction) self.cliplistMenu.addAction(self.removeAllAction) def setRunningTime(self, runtime: str) -> None: self.runtimeLabel.setText('<div align="right">%s</div>' % runtime) @pyqtSlot(int) def setNoVideoText(self, frame: int) -> None: self.novideoLabel.setPixmap(self.novideoMovie.currentPixmap()) def itemMenu(self, pos: QPoint) -> None: globalPos = self.cliplist.mapToGlobal(pos) self.moveItemUpAction.setEnabled(False) self.moveItemDownAction.setEnabled(False) self.removeItemAction.setEnabled(False) self.removeAllAction.setEnabled(False) index = self.cliplist.currentRow() if index != -1: if not self.inCut: if index > 0: self.moveItemUpAction.setEnabled(True) if index < self.cliplist.count() - 1: self.moveItemDownAction.setEnabled(True) if self.cliplist.count() > 0: self.removeItemAction.setEnabled(True) if self.cliplist.count() > 0: self.removeAllAction.setEnabled(True) self.cliplistMenu.exec_(globalPos) def moveItemUp(self) -> None: index = self.cliplist.currentRow() tmpItem = self.clipTimes[index] del self.clipTimes[index] self.clipTimes.insert(index - 1, tmpItem) self.renderTimes() def moveItemDown(self) -> None: index = self.cliplist.currentRow() tmpItem = self.clipTimes[index] del self.clipTimes[index] self.clipTimes.insert(index + 1, tmpItem) self.renderTimes() def removeItem(self) -> None: index = self.cliplist.currentRow() del self.clipTimes[index] if self.inCut and index == self.cliplist.count() - 1: self.inCut = False self.initMediaControls() self.renderTimes() def clearList(self) -> None: self.clipTimes.clear() self.cliplist.clear() self.inCut = False self.renderTimes() self.initMediaControls() def mediaInfo(self) -> None: if self.mediaPlayer.isMetaDataAvailable(): content = '<table cellpadding="4">' for key in self.mediaPlayer.availableMetaData(): val = self.mediaPlayer.metaData(key) if type(val) is QSize: val = '%s x %s' % (val.width(), val.height()) content += '<tr><td align="right"><b>%s:</b></td><td>%s</td></tr>\n' % ( key, val) content += '</table>' mbox = QMessageBox(windowTitle='Media Information', windowIcon=self.parent.windowIcon(), textFormat=Qt.RichText) mbox.setText('<b>%s</b>' % os.path.basename( self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile())) mbox.setInformativeText(content) mbox.exec_() else: QMessageBox.critical( self.parent, 'Could not retrieve media information', '''There was a problem in tring to retrieve media information. This DOES NOT mean there is a problem with the file and you should be able to continue using it.''') def aboutInfo(self) -> None: about_html = '''<style> a { color:#441d4e; text-decoration:none; font-weight:bold; } a:hover { text-decoration:underline; } </style> <p style="font-size:26pt; font-weight:bold;">%s</p> <p> <span style="font-size:13pt;"><b>Version: %s</b></span> <span style="font-size:10pt;position:relative;left:5px;">( %s )</span> </p> <p style="font-size:13px;"> Copyright © 2016 <a href="mailto:[email protected]">Pete Alexandrou</a> <br/> Website: <a href="%s">%s</a> </p> <p style="font-size:13px;"> Thanks to the folks behind the <b>Qt</b>, <b>PyQt</b> and <b>FFmpeg</b> projects for all their hard and much appreciated work. </p> <p style="font-size:11px;"> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. </p> <p style="font-size:11px;"> This software uses libraries from the <a href="https://www.ffmpeg.org">FFmpeg</a> project under the <a href="https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html">LGPLv2.1</a> </p>''' % (qApp.applicationName(), qApp.applicationVersion(), platform.architecture()[0], qApp.organizationDomain(), qApp.organizationDomain()) QMessageBox.about(self.parent, 'About %s' % qApp.applicationName(), about_html) def openFile(self) -> None: filename, _ = QFileDialog.getOpenFileName(self.parent, caption='Select video', directory=QDir.homePath()) if filename != '': self.loadFile(filename) def loadFile(self, filename: str) -> None: self.movieFilename = filename if not os.path.exists(filename): return self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(filename))) self.initMediaControls(True) self.cliplist.clear() self.clipTimes = [] self.parent.setWindowTitle( '%s - %s' % (qApp.applicationName(), os.path.basename(filename))) if not self.movieLoaded: self.videoLayout.replaceWidget(self.novideoWidget, self.videoplayerWidget) self.novideoMovie.stop() self.novideoMovie.deleteLater() self.novideoWidget.deleteLater() self.videoplayerWidget.show() self.videoWidget.show() self.movieLoaded = True if self.mediaPlayer.isVideoAvailable(): self.mediaPlayer.setPosition(1) self.mediaPlayer.play() self.mediaPlayer.pause() def playVideo(self) -> None: if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.mediaPlayer.pause() self.playAction.setText('Play Video') else: self.mediaPlayer.play() self.playAction.setText('Pause Video') def initMediaControls(self, flag: bool = True) -> None: self.playAction.setEnabled(flag) self.saveAction.setEnabled(False) self.cutStartAction.setEnabled(flag) self.cutEndAction.setEnabled(False) self.mediaInfoAction.setEnabled(flag) if flag: self.seekSlider.setRestrictValue(0) def setPosition(self, position: int) -> None: self.mediaPlayer.setPosition(position) def positionChanged(self, progress: int) -> None: self.seekSlider.setValue(progress) currentTime = self.deltaToQTime(progress) totalTime = self.deltaToQTime(self.mediaPlayer.duration()) self.timeCounter.setText('%s / %s' % (currentTime.toString( self.timeformat), totalTime.toString(self.timeformat))) @pyqtSlot() def mediaStateChanged(self) -> None: if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.playAction.setIcon(self.pauseIcon) else: self.playAction.setIcon(self.playIcon) def durationChanged(self, duration: int) -> None: self.seekSlider.setRange(0, duration) def muteAudio(self, muted: bool) -> None: if self.mediaPlayer.isMuted(): self.mediaPlayer.setMuted(not self.mediaPlayer.isMuted()) self.muteButton.setIcon(self.unmuteIcon) self.muteButton.setToolTip('Mute') else: self.mediaPlayer.setMuted(not self.mediaPlayer.isMuted()) self.muteButton.setIcon(self.muteIcon) self.muteButton.setToolTip('Unmute') def setVolume(self, volume: int) -> None: self.mediaPlayer.setVolume(volume) def toggleFullscreen(self) -> None: self.videoWidget.setFullScreen(not self.videoWidget.isFullScreen()) def cutStart(self) -> None: self.clipTimes.append([ self.deltaToQTime(self.mediaPlayer.position()), '', self.captureImage() ]) self.cutStartAction.setDisabled(True) self.cutEndAction.setEnabled(True) self.seekSlider.setRestrictValue(self.seekSlider.value() + 1000) self.mediaPlayer.setPosition(self.seekSlider.restrictValue) self.inCut = True self.renderTimes() def cutEnd(self) -> None: item = self.clipTimes[len(self.clipTimes) - 1] selected = self.deltaToQTime(self.mediaPlayer.position()) if selected.__lt__(item[0]): QMessageBox.critical( self.parent, 'Invalid END Time', 'The clip end time must come AFTER it\'s start time. Please try again.' ) return item[1] = selected self.cutStartAction.setEnabled(True) self.cutEndAction.setDisabled(True) self.seekSlider.setRestrictValue(0) self.inCut = False self.renderTimes() @pyqtSlot(QModelIndex, int, int, QModelIndex, int) def syncClipList(self, parent: QModelIndex, start: int, end: int, destination: QModelIndex, row: int) -> None: if start < row: index = row - 1 else: index = row clip = self.clipTimes.pop(start) self.clipTimes.insert(index, clip) def renderTimes(self) -> None: self.cliplist.clear() self.seekSlider.setCutMode(self.inCut) if len(self.clipTimes) > 4: self.cliplist.setFixedWidth(200) else: self.cliplist.setFixedWidth(185) self.totalRuntime = 0 for item in self.clipTimes: endItem = '' if type(item[1]) is QTime: endItem = item[1].toString(self.timeformat) self.totalRuntime += item[0].msecsTo(item[1]) listitem = QListWidgetItem() listitem.setTextAlignment(Qt.AlignVCenter) if type(item[2]) is QPixmap: listitem.setIcon(QIcon(item[2])) self.cliplist.addItem(listitem) marker = QLabel( '''<style>b { font-size:8pt; } p { margin:5px; }</style> <p><b>START</b><br/>%s</p><p><b>END</b><br/>%s</p>''' % (item[0].toString(self.timeformat), endItem)) self.cliplist.setItemWidget(listitem, marker) listitem.setFlags(Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsEnabled) if len(self.clipTimes) and not self.inCut: self.saveAction.setEnabled(True) if self.inCut or len(self.clipTimes) == 0 or not type( self.clipTimes[0][1]) is QTime: self.saveAction.setEnabled(False) self.setRunningTime( self.deltaToQTime(self.totalRuntime).toString(self.timeformat)) @staticmethod def deltaToQTime(millisecs: int) -> QTime: secs = millisecs / 1000 return QTime((secs / 3600) % 60, (secs / 60) % 60, secs % 60, (secs * 1000) % 1000) def captureImage(self) -> None: frametime = self.deltaToQTime( self.mediaPlayer.position()).addSecs(1).toString(self.timeformat) inputfile = self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile( ) imagecap = self.videoService.capture(inputfile, frametime) if type(imagecap) is QPixmap: return imagecap def cutVideo(self) -> bool: self.setCursor(Qt.BusyCursor) clips = len(self.clipTimes) filename, filelist = '', [] source = self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile() _, sourceext = os.path.splitext(source) if clips > 0: self.finalFilename, _ = QFileDialog.getSaveFileName( self.parent, 'Save video', source, 'Video files (*%s)' % sourceext) if self.finalFilename != '': self.saveAction.setDisabled(True) self.showProgress(clips) file, ext = os.path.splitext(self.finalFilename) index = 1 self.progress.setLabelText('Cutting video clips...') qApp.processEvents() for clip in self.clipTimes: duration = self.deltaToQTime(clip[0].msecsTo( clip[1])).toString(self.timeformat) filename = '%s_%s%s' % (file, '{0:0>2}'.format(index), ext) filelist.append(filename) self.videoService.cut(source, filename, clip[0].toString(self.timeformat), duration) index += 1 if len(filelist) > 1: self.joinVideos(filelist, self.finalFilename) else: QFile.remove(self.finalFilename) QFile.rename(filename, self.finalFilename) self.unsetCursor() self.progress.setLabelText('Complete...') qApp.processEvents() self.saveAction.setEnabled(True) self.progress.close() self.progress.deleteLater() self.complete() self.saveAction.setEnabled(True) self.unsetCursor() self.saveAction.setDisabled(True) return True self.unsetCursor() self.saveAction.setDisabled(True) return False def joinVideos(self, joinlist: list, filename: str) -> None: listfile = os.path.normpath( os.path.join(os.path.dirname(joinlist[0]), '.vidcutter.list')) fobj = open(listfile, 'w') for file in joinlist: fobj.write('file \'%s\'\n' % file.replace("'", "\\'")) fobj.close() self.videoService.join(listfile, filename) try: QFile.remove(listfile) for file in joinlist: if os.path.isfile(file): QFile.remove(file) except: pass def showProgress(self, steps: int, label: str = 'Processing video...') -> None: self.progress = QProgressDialog(label, None, 0, steps, self.parent, windowModality=Qt.ApplicationModal, windowIcon=self.parent.windowIcon(), minimumDuration=0, minimumWidth=500) self.progress.show() for i in range(steps): self.progress.setValue(i) qApp.processEvents() time.sleep(1) def complete(self) -> None: info = QFileInfo(self.finalFilename) mbox = QMessageBox(windowTitle='Success', windowIcon=self.parent.windowIcon(), minimumWidth=500, iconPixmap=self.successIcon.pixmap(48, 49), textFormat=Qt.RichText) mbox.setText( ''' <style> table.info { margin:8px; padding:4px 15px; } td.label { font-weight:bold; font-size:9pt; text-align:right; background-color:#444; color:#FFF; } td.value { background-color:#FFF !important; font-size:10pt; } </style> <p>Your video was successfully created.</p> <p align="center"> <table class="info" cellpadding="6" cellspacing="0"> <tr> <td class="label"><b>Filename</b></td> <td class="value" nowrap>%s</td> </tr> <tr> <td class="label"><b>Size</b></td> <td class="value">%s</td> </tr> <tr> <td class="label"><b>Runtime</b></td> <td class="value">%s</td> </tr> </table> </p> <p>How would you like to proceed?</p>''' % (QDir.toNativeSeparators( self.finalFilename), self.sizeof_fmt(int(info.size())), self.deltaToQTime(self.totalRuntime).toString(self.timeformat))) play = mbox.addButton('Play', QMessageBox.AcceptRole) play.setIcon(self.completePlayIcon) play.clicked.connect(self.openResult) fileman = mbox.addButton('Open', QMessageBox.AcceptRole) fileman.setIcon(self.completeOpenIcon) fileman.clicked.connect(self.openFolder) end = mbox.addButton('Exit', QMessageBox.AcceptRole) end.setIcon(self.completeExitIcon) end.clicked.connect(self.close) new = mbox.addButton('Restart', QMessageBox.AcceptRole) new.setIcon(self.completeRestartIcon) new.clicked.connect(self.startNew) mbox.setDefaultButton(new) mbox.setEscapeButton(new) mbox.exec_() def sizeof_fmt(self, num: float, suffix: chr = 'B') -> str: for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 return "%.1f%s%s" % (num, 'Y', suffix) @pyqtSlot() def openFolder(self) -> None: self.openResult(pathonly=True) @pyqtSlot(bool) def openResult(self, pathonly: bool = False) -> None: self.startNew() if len(self.finalFilename) and os.path.exists(self.finalFilename): target = self.finalFilename if not pathonly else os.path.dirname( self.finalFilename) QDesktopServices.openUrl(QUrl.fromLocalFile(target)) @pyqtSlot() def startNew(self) -> None: self.unsetCursor() self.clearList() self.seekSlider.setValue(0) self.seekSlider.setRange(0, 0) self.mediaPlayer.setMedia(QMediaContent()) self.initNoVideo() self.videoLayout.replaceWidget(self.videoplayerWidget, self.novideoWidget) self.initMediaControls(False) self.parent.setWindowTitle('%s' % qApp.applicationName()) def wheelEvent(self, event: QWheelEvent) -> None: if self.mediaPlayer.isVideoAvailable( ) or self.mediaPlayer.isAudioAvailable(): if event.angleDelta().y() > 0: newval = self.seekSlider.value() - 1000 else: newval = self.seekSlider.value() + 1000 self.seekSlider.setValue(newval) self.seekSlider.setSliderPosition(newval) self.mediaPlayer.setPosition(newval) event.accept() def keyPressEvent(self, event: QKeyEvent) -> None: if self.mediaPlayer.isVideoAvailable( ) or self.mediaPlayer.isAudioAvailable(): addtime = 0 if event.key() == Qt.Key_Left: addtime = -1000 elif event.key() == Qt.Key_PageUp or event.key() == Qt.Key_Up: addtime = -10000 elif event.key() == Qt.Key_Right: addtime = 1000 elif event.key() == Qt.Key_PageDown or event.key() == Qt.Key_Down: addtime = 10000 elif event.key() == Qt.Key_Enter: self.toggleFullscreen() elif event.key( ) == Qt.Key_Escape and self.videoWidget.isFullScreen(): self.videoWidget.setFullScreen(False) if addtime != 0: newval = self.seekSlider.value() + addtime self.seekSlider.setValue(newval) self.seekSlider.setSliderPosition(newval) self.mediaPlayer.setPosition(newval) event.accept() def mousePressEvent(self, event: QMouseEvent) -> None: if event.button() == Qt.BackButton and self.cutStartAction.isEnabled(): self.cutStart() event.accept() elif event.button( ) == Qt.ForwardButton and self.cutEndAction.isEnabled(): self.cutEnd() event.accept() else: super(VidCutter, self).mousePressEvent(event) def eventFilter(self, obj: QObject, event: QEvent) -> bool: if event.type() == QEvent.MouseButtonRelease and isinstance( obj, VideoSlider): if obj.objectName() == 'VideoSlider' and ( self.mediaPlayer.isVideoAvailable() or self.mediaPlayer.isAudioAvailable()): obj.setValue( QStyle.sliderValueFromPosition(obj.minimum(), obj.maximum(), event.x(), obj.width())) self.mediaPlayer.setPosition(obj.sliderPosition()) return QWidget.eventFilter(self, obj, event) @pyqtSlot(QMediaPlayer.Error) def handleError(self, error: QMediaPlayer.Error) -> None: self.unsetCursor() self.startNew() if error == QMediaPlayer.ResourceError: QMessageBox.critical( self.parent, 'Error', 'Invalid media file detected at:<br/><br/><b>%s</b><br/><br/>%s' % (self.movieFilename, self.mediaPlayer.errorString())) else: QMessageBox.critical(self.parent, 'Error', self.mediaPlayer.errorString()) def getAppPath(self) -> str: return ':' def closeEvent(self, event: QCloseEvent) -> None: self.parent.closeEvent(event)
class VidCutter(QWidget): def __init__(self, parent): super(VidCutter, self).__init__(parent) self.novideoWidget = QWidget(self, autoFillBackground=True) self.parent = parent self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface) self.videoWidget = VideoWidget(self) self.videoService = VideoService(self) QFontDatabase.addApplicationFont( MainWindow.get_path('fonts/DroidSansMono.ttf')) QFontDatabase.addApplicationFont( MainWindow.get_path('fonts/OpenSans.ttf')) fontSize = 12 if sys.platform == 'darwin' else 10 appFont = QFont('Open Sans', fontSize, 300) qApp.setFont(appFont) self.clipTimes = [] self.inCut = False self.movieFilename = '' self.movieLoaded = False self.timeformat = 'hh:mm:ss' self.finalFilename = '' self.totalRuntime = 0 self.initIcons() self.initActions() self.toolbar = QToolBar(floatable=False, movable=False, iconSize=QSize(40, 36)) self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toolbar.setStyleSheet('''QToolBar { spacing:10px; } QToolBar QToolButton { border:1px solid transparent; min-width:95px; font-size:11pt; font-weight:400; border-radius:5px; padding:1px 2px; color:#444; } QToolBar QToolButton:hover { border:1px inset #6A4572; color:#6A4572; background-color:rgba(255, 255, 255, 0.85); } QToolBar QToolButton:pressed { border:1px inset #6A4572; color:#6A4572; background-color:rgba(255, 255, 255, 0.25); } QToolBar QToolButton:disabled { color:#999; }''') self.initToolbar() self.appMenu, self.cliplistMenu = QMenu(), QMenu() self.initMenus() self.seekSlider = VideoSlider(parent=self, sliderMoved=self.setPosition) self.initNoVideo() self.cliplist = QListWidget( sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding), contextMenuPolicy=Qt.CustomContextMenu, uniformItemSizes=True, iconSize=QSize(100, 700), dragDropMode=QAbstractItemView.InternalMove, alternatingRowColors=True, customContextMenuRequested=self.itemMenu, dragEnabled=True) self.cliplist.setStyleSheet( 'QListView { border-radius:0; border:none; border-left:1px solid #B9B9B9; ' + 'border-right:1px solid #B9B9B9; } QListView::item { padding:10px 0; }' ) self.cliplist.setFixedWidth(185) self.cliplist.model().rowsMoved.connect(self.syncClipList) listHeader = QLabel(pixmap=QPixmap( MainWindow.get_path('images/clipindex.png'), 'PNG'), alignment=Qt.AlignCenter) listHeader.setStyleSheet( '''padding:5px; padding-top:8px; border:1px solid #b9b9b9; background-color:qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #FFF, stop: 0.5 #EAEAEA, stop: 0.6 #EAEAEA stop:1 #FFF);''' ) self.runtimeLabel = QLabel('<div align="right">00:00:00</div>', textFormat=Qt.RichText) self.runtimeLabel.setStyleSheet( '''font-family:Droid Sans Mono; font-size:10pt; color:#FFF; background:rgb(106, 69, 114) url(:images/runtime.png) no-repeat left center; padding:2px; padding-right:8px; border:1px solid #B9B9B9;''') self.clipindexLayout = QVBoxLayout(spacing=0) self.clipindexLayout.setContentsMargins(0, 0, 0, 0) self.clipindexLayout.addWidget(listHeader) self.clipindexLayout.addWidget(self.cliplist) self.clipindexLayout.addWidget(self.runtimeLabel) self.videoLayout = QHBoxLayout() self.videoLayout.setContentsMargins(0, 0, 0, 0) self.videoLayout.addWidget(self.novideoWidget) self.videoLayout.addLayout(self.clipindexLayout) self.timeCounter = QLabel('00:00:00 / 00:00:00', autoFillBackground=True, alignment=Qt.AlignCenter, sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Fixed)) self.timeCounter.setStyleSheet( 'color:#FFF; background:#000; font-family:Droid Sans Mono; font-size:10.5pt; padding:4px;' ) videoplayerLayout = QVBoxLayout(spacing=0) videoplayerLayout.setContentsMargins(0, 0, 0, 0) videoplayerLayout.addWidget(self.videoWidget) videoplayerLayout.addWidget(self.timeCounter) self.videoplayerWidget = QWidget(self, visible=False) self.videoplayerWidget.setLayout(videoplayerLayout) self.muteButton = QPushButton(icon=self.unmuteIcon, flat=True, toolTip='Mute', statusTip='Toggle audio mute', iconSize=QSize(16, 16), cursor=Qt.PointingHandCursor, clicked=self.muteAudio) self.volumeSlider = QSlider(Qt.Horizontal, toolTip='Volume', statusTip='Adjust volume level', cursor=Qt.PointingHandCursor, value=50, minimum=0, maximum=100, sliderMoved=self.setVolume) self.menuButton = QPushButton( icon=self.menuIcon, flat=True, toolTip='Menu', statusTip='Media + application information', iconSize=QSize(24, 24), cursor=Qt.PointingHandCursor) self.menuButton.setMenu(self.appMenu) toolbarLayout = QHBoxLayout() toolbarLayout.addWidget(self.toolbar) toolbarLayout.setContentsMargins(2, 2, 2, 2) toolbarGroup = QGroupBox() toolbarGroup.setFlat(False) toolbarGroup.setCursor(Qt.PointingHandCursor) toolbarGroup.setLayout(toolbarLayout) toolbarGroup.setStyleSheet( '''QGroupBox { background-color:rgba(0, 0, 0, 0.1); border:1px inset #888; border-radius:5px; }''') controlsLayout = QHBoxLayout(spacing=0) controlsLayout.addStretch(1) controlsLayout.addWidget(toolbarGroup) controlsLayout.addStretch(1) controlsLayout.addWidget(self.muteButton) controlsLayout.addWidget(self.volumeSlider) controlsLayout.addSpacing(1) controlsLayout.addWidget(self.menuButton) layout = QVBoxLayout() layout.setContentsMargins(10, 10, 10, 4) layout.addLayout(self.videoLayout) layout.addWidget(self.seekSlider) layout.addSpacing(5) layout.addLayout(controlsLayout) layout.addSpacing(2) self.setLayout(layout) self.mediaPlayer.setVideoOutput(self.videoWidget) self.mediaPlayer.stateChanged.connect(self.mediaStateChanged) self.mediaPlayer.positionChanged.connect(self.positionChanged) self.mediaPlayer.durationChanged.connect(self.durationChanged) self.mediaPlayer.error.connect(self.handleError) def initNoVideo(self) -> None: novideoImage = QLabel( alignment=Qt.AlignCenter, autoFillBackground=False, pixmap=QPixmap(MainWindow.get_path('images/novideo.png'), 'PNG'), sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.MinimumExpanding)) novideoImage.setBackgroundRole(QPalette.Dark) novideoImage.setContentsMargins(0, 20, 0, 20) self.novideoLabel = QLabel(alignment=Qt.AlignCenter, autoFillBackground=True, sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Minimum)) self.novideoLabel.setBackgroundRole(QPalette.Dark) self.novideoLabel.setContentsMargins(0, 20, 15, 60) novideoLayout = QVBoxLayout(spacing=0) novideoLayout.addWidget(novideoImage) novideoLayout.addWidget(self.novideoLabel, alignment=Qt.AlignTop) self.novideoMovie = QMovie( MainWindow.get_path('images/novideotext.gif')) self.novideoMovie.frameChanged.connect(self.setNoVideoText) self.novideoMovie.start() self.novideoWidget.setBackgroundRole(QPalette.Dark) self.novideoWidget.setLayout(novideoLayout) def initIcons(self) -> None: self.appIcon = QIcon(MainWindow.get_path('images/vidcutter.png')) self.openIcon = icon('fa.film', color='#444', color_active='#6A4572', scale_factor=0.9) self.playIcon = icon('fa.play-circle-o', color='#444', color_active='#6A4572', scale_factor=1.1) self.pauseIcon = icon('fa.pause-circle-o', color='#444', color_active='#6A4572', scale_factor=1.1) self.cutStartIcon = icon('fa.scissors', scale_factor=1.15, color='#444', color_active='#6A4572') endicon_normal = icon('fa.scissors', scale_factor=1.15, color='#444').pixmap(QSize(36, 36)).toImage() endicon_active = icon('fa.scissors', scale_factor=1.15, color='#6A4572').pixmap(QSize(36, 36)).toImage() self.cutEndIcon = QIcon() self.cutEndIcon.addPixmap( QPixmap.fromImage( endicon_normal.mirrored(horizontal=True, vertical=False)), QIcon.Normal, QIcon.Off) self.cutEndIcon.addPixmap( QPixmap.fromImage( endicon_active.mirrored(horizontal=True, vertical=False)), QIcon.Active, QIcon.Off) self.saveIcon = icon('fa.video-camera', color='#6A4572', color_active='#6A4572') self.muteIcon = QIcon(MainWindow.get_path('images/muted.png')) self.unmuteIcon = QIcon(MainWindow.get_path('images/unmuted.png')) self.upIcon = icon('ei.caret-up', color='#444') self.downIcon = icon('ei.caret-down', color='#444') self.removeIcon = icon('ei.remove', color='#B41D1D') self.removeAllIcon = icon('ei.trash', color='#B41D1D') self.successIcon = QIcon(MainWindow.get_path('images/success.png')) self.menuIcon = icon('fa.cog', color='#444', scale_factor=1.15) self.completePlayIcon = icon('fa.play', color='#444') self.completeOpenIcon = icon('fa.folder-open', color='#444') self.completeRestartIcon = icon('fa.retweet', color='#444') self.completeExitIcon = icon('fa.sign-out', color='#444') self.mediaInfoIcon = icon('fa.info-circle', color='#444') self.updateCheckIcon = icon('fa.cloud-download', color='#444') def initActions(self) -> None: self.openAction = QAction(self.openIcon, 'Open', self, statusTip='Open media file', triggered=self.openMedia) self.playAction = QAction(self.playIcon, 'Play', self, statusTip='Play media file', triggered=self.playMedia, enabled=False) self.cutStartAction = QAction(self.cutStartIcon, ' Start', self, toolTip='Start', statusTip='Set clip start marker', triggered=self.setCutStart, enabled=False) self.cutEndAction = QAction(self.cutEndIcon, ' End', self, toolTip='End', statusTip='Set clip end marker', triggered=self.setCutEnd, enabled=False) self.saveAction = QAction(self.saveIcon, 'Save', self, statusTip='Save clips to a new video file', triggered=self.cutVideo, enabled=False) self.moveItemUpAction = QAction( self.upIcon, 'Move up', self, statusTip='Move clip position up in list', triggered=self.moveItemUp, enabled=False) self.moveItemDownAction = QAction( self.downIcon, 'Move down', self, statusTip='Move clip position down in list', triggered=self.moveItemDown, enabled=False) self.removeItemAction = QAction( self.removeIcon, 'Remove clip', self, statusTip='Remove selected clip from list', triggered=self.removeItem, enabled=False) self.removeAllAction = QAction(self.removeAllIcon, 'Clear list', self, statusTip='Clear all clips from list', triggered=self.clearList, enabled=False) self.mediaInfoAction = QAction( self.mediaInfoIcon, 'Media information', self, statusTip='View current media file information', triggered=self.mediaInfo, enabled=False) self.updateCheckAction = QAction( self.updateCheckIcon, 'Check for updates...', self, statusTip='Check for application updates', triggered=self.updateCheck) self.aboutQtAction = QAction('About Qt', self, statusTip='About Qt', triggered=qApp.aboutQt) self.aboutAction = QAction('About %s' % qApp.applicationName(), self, statusTip='Credits and licensing', triggered=self.aboutInfo) def initToolbar(self) -> None: self.toolbar.addAction(self.openAction) self.toolbar.addAction(self.playAction) self.toolbar.addAction(self.cutStartAction) self.toolbar.addAction(self.cutEndAction) self.toolbar.addAction(self.saveAction) def initMenus(self) -> None: self.appMenu.addAction(self.mediaInfoAction) self.appMenu.addAction(self.updateCheckAction) self.appMenu.addSeparator() self.appMenu.addAction(self.aboutQtAction) self.appMenu.addAction(self.aboutAction) self.cliplistMenu.addAction(self.moveItemUpAction) self.cliplistMenu.addAction(self.moveItemDownAction) self.cliplistMenu.addSeparator() self.cliplistMenu.addAction(self.removeItemAction) self.cliplistMenu.addAction(self.removeAllAction) @staticmethod def getSpacer() -> QWidget: spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) return spacer def setRunningTime(self, runtime: str) -> None: self.runtimeLabel.setText('<div align="right">%s</div>' % runtime) @pyqtSlot(int) def setNoVideoText(self) -> None: self.novideoLabel.setPixmap(self.novideoMovie.currentPixmap()) def itemMenu(self, pos: QPoint) -> None: globalPos = self.cliplist.mapToGlobal(pos) self.moveItemUpAction.setEnabled(False) self.moveItemDownAction.setEnabled(False) self.removeItemAction.setEnabled(False) self.removeAllAction.setEnabled(False) index = self.cliplist.currentRow() if index != -1: if not self.inCut: if index > 0: self.moveItemUpAction.setEnabled(True) if index < self.cliplist.count() - 1: self.moveItemDownAction.setEnabled(True) if self.cliplist.count() > 0: self.removeItemAction.setEnabled(True) if self.cliplist.count() > 0: self.removeAllAction.setEnabled(True) self.cliplistMenu.exec_(globalPos) def moveItemUp(self) -> None: index = self.cliplist.currentRow() tmpItem = self.clipTimes[index] del self.clipTimes[index] self.clipTimes.insert(index - 1, tmpItem) self.renderTimes() def moveItemDown(self) -> None: index = self.cliplist.currentRow() tmpItem = self.clipTimes[index] del self.clipTimes[index] self.clipTimes.insert(index + 1, tmpItem) self.renderTimes() def removeItem(self) -> None: index = self.cliplist.currentRow() del self.clipTimes[index] if self.inCut and index == self.cliplist.count() - 1: self.inCut = False self.initMediaControls() self.renderTimes() def clearList(self) -> None: self.clipTimes.clear() self.cliplist.clear() self.inCut = False self.renderTimes() self.initMediaControls() def mediaInfo(self) -> None: if self.mediaPlayer.isMetaDataAvailable(): content = '<table cellpadding="4">' for key in self.mediaPlayer.availableMetaData(): val = self.mediaPlayer.metaData(key) if type(val) is QSize: val = '%s x %s' % (val.width(), val.height()) content += '<tr><td align="right"><b>%s:</b></td><td>%s</td></tr>\n' % ( key, val) content += '</table>' mbox = QMessageBox(windowTitle='Media Information', windowIcon=self.parent.windowIcon(), textFormat=Qt.RichText) mbox.setText('<b>%s</b>' % os.path.basename( self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile())) mbox.setInformativeText(content) mbox.exec_() else: QMessageBox.critical( self.parent, 'MEDIA ERROR', '<h3>Could not probe media file.</h3>' + '<p>An error occurred while analyzing the media file for its metadata details.' + '<br/><br/><b>This DOES NOT mean there is a problem with the file and you should ' + 'be able to continue using it.</b></p>') def aboutInfo(self) -> None: about_html = '''<style> a { color:#441d4e; text-decoration:none; font-weight:bold; } a:hover { text-decoration:underline; } </style> <div style="min-width:650px;"> <p style="font-size:26pt; font-weight:bold; color:#6A4572;">%s</p> <p> <span style="font-size:13pt;"><b>Version: %s</b></span> <span style="font-size:10pt;position:relative;left:5px;">( %s )</span> </p> <p style="font-size:13px;"> Copyright © 2016 <a href="mailto:[email protected]">Pete Alexandrou</a> <br/> Website: <a href="%s">%s</a> </p> <p style="font-size:13px;"> Thanks to the folks behind the <b>Qt</b>, <b>PyQt</b> and <b>FFmpeg</b> projects for all their hard and much appreciated work. </p> <p style="font-size:11px;"> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. </p> <p style="font-size:11px;"> This software uses libraries from the <a href="https://www.ffmpeg.org">FFmpeg</a> project under the <a href="https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html">LGPLv2.1</a> </p></div>''' % (qApp.applicationName(), qApp.applicationVersion(), platform.architecture()[0], qApp.organizationDomain(), qApp.organizationDomain()) QMessageBox.about(self.parent, 'About %s' % qApp.applicationName(), about_html) def openMedia(self) -> None: filename, _ = QFileDialog.getOpenFileName(self.parent, caption='Select video', directory=QDir.homePath()) if filename != '': self.loadFile(filename) def loadFile(self, filename: str) -> None: self.movieFilename = filename if not os.path.exists(filename): return self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(filename))) self.initMediaControls(True) self.cliplist.clear() self.clipTimes = [] self.parent.setWindowTitle( '%s - %s' % (qApp.applicationName(), os.path.basename(filename))) if not self.movieLoaded: self.videoLayout.replaceWidget(self.novideoWidget, self.videoplayerWidget) self.novideoMovie.stop() self.novideoMovie.deleteLater() self.novideoWidget.deleteLater() self.videoplayerWidget.show() self.videoWidget.show() self.movieLoaded = True if self.mediaPlayer.isVideoAvailable(): self.mediaPlayer.setPosition(1) self.mediaPlayer.play() self.mediaPlayer.pause() def playMedia(self) -> None: if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.mediaPlayer.pause() self.playAction.setText('Play') else: self.mediaPlayer.play() self.playAction.setText('Pause') def initMediaControls(self, flag: bool = True) -> None: self.playAction.setEnabled(flag) self.saveAction.setEnabled(False) self.cutStartAction.setEnabled(flag) self.cutEndAction.setEnabled(False) self.mediaInfoAction.setEnabled(flag) if flag: self.seekSlider.setRestrictValue(0) def setPosition(self, position: int) -> None: self.mediaPlayer.setPosition(position) def positionChanged(self, progress: int) -> None: self.seekSlider.setValue(progress) currentTime = self.deltaToQTime(progress) totalTime = self.deltaToQTime(self.mediaPlayer.duration()) self.timeCounter.setText('%s / %s' % (currentTime.toString( self.timeformat), totalTime.toString(self.timeformat))) @pyqtSlot() def mediaStateChanged(self) -> None: if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.playAction.setIcon(self.pauseIcon) else: self.playAction.setIcon(self.playIcon) def durationChanged(self, duration: int) -> None: self.seekSlider.setRange(0, duration) def muteAudio(self) -> None: if self.mediaPlayer.isMuted(): self.muteButton.setIcon(self.unmuteIcon) self.muteButton.setToolTip('Mute') else: self.muteButton.setIcon(self.muteIcon) self.muteButton.setToolTip('Unmute') self.mediaPlayer.setMuted(not self.mediaPlayer.isMuted()) def setVolume(self, volume: int) -> None: self.mediaPlayer.setVolume(volume) def toggleFullscreen(self) -> None: self.videoWidget.setFullScreen(not self.videoWidget.isFullScreen()) def setCutStart(self) -> None: self.clipTimes.append([ self.deltaToQTime(self.mediaPlayer.position()), '', self.captureImage() ]) self.cutStartAction.setDisabled(True) self.cutEndAction.setEnabled(True) self.seekSlider.setRestrictValue(self.seekSlider.value(), True) self.inCut = True self.renderTimes() def setCutEnd(self) -> None: item = self.clipTimes[len(self.clipTimes) - 1] selected = self.deltaToQTime(self.mediaPlayer.position()) if selected.__lt__(item[0]): QMessageBox.critical( self.parent, 'Invalid END Time', 'The clip end time must come AFTER it\'s start time. Please try again.' ) return item[1] = selected self.cutStartAction.setEnabled(True) self.cutEndAction.setDisabled(True) self.seekSlider.setRestrictValue(0, False) self.inCut = False self.renderTimes() @pyqtSlot(QModelIndex, int, int, QModelIndex, int) def syncClipList(self, parent: QModelIndex, start: int, end: int, destination: QModelIndex, row: int) -> None: if start < row: index = row - 1 else: index = row clip = self.clipTimes.pop(start) self.clipTimes.insert(index, clip) def renderTimes(self) -> None: self.cliplist.clear() if len(self.clipTimes) > 4: self.cliplist.setFixedWidth(200) else: self.cliplist.setFixedWidth(185) self.totalRuntime = 0 for item in self.clipTimes: endItem = '' if type(item[1]) is QTime: endItem = item[1].toString(self.timeformat) self.totalRuntime += item[0].msecsTo(item[1]) listitem = QListWidgetItem() listitem.setTextAlignment(Qt.AlignVCenter) if type(item[2]) is QPixmap: listitem.setIcon(QIcon(item[2])) self.cliplist.addItem(listitem) marker = QLabel( '''<style>b { font-size:7pt; } p { margin:2px 5px; }</style> <p><b>START</b><br/>%s<br/><b>END</b><br/>%s</p>''' % (item[0].toString(self.timeformat), endItem)) marker.setStyleSheet('border:none;') self.cliplist.setItemWidget(listitem, marker) listitem.setFlags(Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsEnabled) if len(self.clipTimes) and not self.inCut: self.saveAction.setEnabled(True) if self.inCut or len(self.clipTimes) == 0 or not type( self.clipTimes[0][1]) is QTime: self.saveAction.setEnabled(False) self.setRunningTime( self.deltaToQTime(self.totalRuntime).toString(self.timeformat)) @staticmethod def deltaToQTime(millisecs: int) -> QTime: secs = millisecs / 1000 return QTime((secs / 3600) % 60, (secs / 60) % 60, secs % 60, (secs * 1000) % 1000) def captureImage(self) -> QPixmap: frametime = self.deltaToQTime(self.mediaPlayer.position()).toString( self.timeformat) inputfile = self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile( ) imagecap = self.videoService.capture(inputfile, frametime) if type(imagecap) is QPixmap: return imagecap def cutVideo(self) -> bool: clips = len(self.clipTimes) filename, filelist = '', [] source = self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile() _, sourceext = os.path.splitext(source) if clips > 0: self.finalFilename, _ = QFileDialog.getSaveFileName( self.parent, 'Save video', source, 'Video files (*%s)' % sourceext) if self.finalFilename == '': return False qApp.setOverrideCursor(Qt.BusyCursor) self.saveAction.setDisabled(True) self.showProgress(clips) file, ext = os.path.splitext(self.finalFilename) index = 1 self.progress.setLabelText('Cutting media files...') qApp.processEvents() for clip in self.clipTimes: duration = self.deltaToQTime(clip[0].msecsTo( clip[1])).toString(self.timeformat) filename = '%s_%s%s' % (file, '{0:0>2}'.format(index), ext) filelist.append(filename) self.videoService.cut(source, filename, clip[0].toString(self.timeformat), duration) index += 1 if len(filelist) > 1: self.joinVideos(filelist, self.finalFilename) else: QFile.remove(self.finalFilename) QFile.rename(filename, self.finalFilename) self.progress.setLabelText('Complete...') self.progress.setValue(100) qApp.processEvents() self.progress.close() self.progress.deleteLater() qApp.restoreOverrideCursor() self.complete() return True return False def joinVideos(self, joinlist: list, filename: str) -> None: listfile = os.path.normpath( os.path.join(os.path.dirname(joinlist[0]), '.vidcutter.list')) fobj = open(listfile, 'w') for file in joinlist: fobj.write('file \'%s\'\n' % file.replace("'", "\\'")) fobj.close() self.videoService.join(listfile, filename) QFile.remove(listfile) for file in joinlist: if os.path.isfile(file): QFile.remove(file) def updateCheck(self) -> None: self.updater = Updater() self.updater.updateAvailable.connect(self.updateHandler) self.updater.start() def updateHandler(self, updateExists: bool, version: str = None): if updateExists: if Updater.notify_update(self, version) == QMessageBox.AcceptRole: self.updater.install_update(self) else: Updater.notify_no_update(self) def showProgress(self, steps: int, label: str = 'Analyzing media...') -> None: self.progress = QProgressDialog(label, None, 0, steps, self.parent, windowModality=Qt.ApplicationModal, windowIcon=self.parent.windowIcon(), minimumDuration=0, minimumWidth=500) self.progress.show() for i in range(steps): self.progress.setValue(i) qApp.processEvents() time.sleep(1) def complete(self) -> None: info = QFileInfo(self.finalFilename) mbox = QMessageBox(windowTitle='VIDEO PROCESSING COMPLETE', minimumWidth=500, textFormat=Qt.RichText) mbox.setText( ''' <style> table.info { margin:6px; padding:4px 15px; } td.label { font-weight:bold; font-size:10.5pt; text-align:right; } td.value { font-size:10.5pt; } </style> <table class="info" cellpadding="4" cellspacing="0"> <tr> <td class="label"><b>File:</b></td> <td class="value" nowrap>%s</td> </tr> <tr> <td class="label"><b>Size:</b></td> <td class="value">%s</td> </tr> <tr> <td class="label"><b>Length:</b></td> <td class="value">%s</td> </tr> </table><br/>''' % (QDir.toNativeSeparators( self.finalFilename), self.sizeof_fmt(int(info.size())), self.deltaToQTime(self.totalRuntime).toString(self.timeformat))) play = mbox.addButton('Play', QMessageBox.AcceptRole) play.setIcon(self.completePlayIcon) play.clicked.connect(self.openResult) fileman = mbox.addButton('Open', QMessageBox.AcceptRole) fileman.setIcon(self.completeOpenIcon) fileman.clicked.connect(self.openFolder) end = mbox.addButton('Exit', QMessageBox.AcceptRole) end.setIcon(self.completeExitIcon) end.clicked.connect(self.close) new = mbox.addButton('Restart', QMessageBox.AcceptRole) new.setIcon(self.completeRestartIcon) new.clicked.connect(self.parent.restart) mbox.setDefaultButton(new) mbox.setEscapeButton(new) mbox.adjustSize() mbox.exec_() def sizeof_fmt(self, num: float, suffix: chr = 'B') -> str: for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 return "%.1f%s%s" % (num, 'Y', suffix) @pyqtSlot() def openFolder(self) -> None: self.openResult(pathonly=True) @pyqtSlot(bool) def openResult(self, pathonly: bool = False) -> None: self.parent.restart() if len(self.finalFilename) and os.path.exists(self.finalFilename): target = self.finalFilename if not pathonly else os.path.dirname( self.finalFilename) QDesktopServices.openUrl(QUrl.fromLocalFile(target)) @pyqtSlot() def startNew(self) -> None: qApp.restoreOverrideCursor() self.clearList() self.seekSlider.setValue(0) self.seekSlider.setRange(0, 0) self.mediaPlayer.setMedia(QMediaContent()) self.initNoVideo() self.videoLayout.replaceWidget(self.videoplayerWidget, self.novideoWidget) self.initMediaControls(False) self.parent.setWindowTitle('%s' % qApp.applicationName()) def wheelEvent(self, event: QWheelEvent) -> None: if self.mediaPlayer.isVideoAvailable( ) or self.mediaPlayer.isAudioAvailable(): if event.angleDelta().y() > 0: newval = self.seekSlider.value() - 1000 else: newval = self.seekSlider.value() + 1000 self.seekSlider.setValue(newval) self.seekSlider.setSliderPosition(newval) self.mediaPlayer.setPosition(newval) event.accept() def keyPressEvent(self, event: QKeyEvent) -> None: if self.mediaPlayer.isVideoAvailable( ) or self.mediaPlayer.isAudioAvailable(): addtime = 0 if event.key() == Qt.Key_Left: addtime = -1000 elif event.key() == Qt.Key_PageUp or event.key() == Qt.Key_Up: addtime = -10000 elif event.key() == Qt.Key_Right: addtime = 1000 elif event.key() == Qt.Key_PageDown or event.key() == Qt.Key_Down: addtime = 10000 elif event.key() == Qt.Key_Enter: self.toggleFullscreen() elif event.key( ) == Qt.Key_Escape and self.videoWidget.isFullScreen(): self.videoWidget.setFullScreen(False) if addtime != 0: newval = self.seekSlider.value() + addtime self.seekSlider.setValue(newval) self.seekSlider.setSliderPosition(newval) self.mediaPlayer.setPosition(newval) event.accept() def mousePressEvent(self, event: QMouseEvent) -> None: if event.button() == Qt.BackButton and self.cutStartAction.isEnabled(): self.setCutStart() event.accept() elif event.button( ) == Qt.ForwardButton and self.cutEndAction.isEnabled(): self.setCutEnd() event.accept() else: super(VidCutter, self).mousePressEvent(event) @pyqtSlot(QMediaPlayer.Error) def handleError(self, error: QMediaPlayer.Error) -> None: qApp.restoreOverrideCursor() self.startNew() if error == QMediaPlayer.ResourceError: QMessageBox.critical( self.parent, 'INVALID MEDIA', 'Invalid media file detected at:<br/><br/><b>%s</b><br/><br/>%s' % (self.movieFilename, self.mediaPlayer.errorString())) else: QMessageBox.critical(self.parent, 'ERROR NOTIFICATION', self.mediaPlayer.errorString()) def closeEvent(self, event: QCloseEvent) -> None: self.parent.closeEvent(event)
class ArnheimModelSelector(BaseSelector): list_query = None model = None new_form = None # can stay not used def __init__(self, *args, base, add_none=False, variables=None, with_form=True, on_select=None, **kwargs): super().__init__(*args, base=base, **kwargs) self.selected_model = None self.variables = variables self.selector = None self.model_selector = None self.with_form = with_form self.add_none = add_none self.on_select = on_select self.loadModels() self.layout = QHBoxLayout() self.setLayout(self.layout) self.buildOrReplaceSelector() self.buildButton() def loadModels(self): self.models = QueryList(self.list_query, self.model).run(variables=self.variables) def requestNewModel(self): form = self.new_form(base=self.base, parent=self) model = form.getModel() if model: self.models = [ model ] + self.models # We are adding it to the first list self.buildOrReplaceSelector() def buildOrReplaceSelector(self): assert self.models is not None, "Please load Models beforee" model_selector = QComboBox() if self.add_none: model_selector.addItem("-----") for model in self.models: model_selector.addItem(model.name) model_selector.currentIndexChanged.connect(self.indexChanged) if len(self.models) > 0: self.selected_model = self.models[ 0] # We automatically select the first item once rebuilding if not self.model_selector: self.layout.addWidget(model_selector) self.model_selector = model_selector else: self.layout.replaceWidget(self.model_selector, model_selector) self.model_selector.close() self.model_selector.setParent(None) self.model_selector = model_selector self.layout.update() return self.model_selector def buildButton(self): if self.new_form and self.with_form: new_model_button = QPushButton("+") new_model_button.clicked.connect(self.requestNewModel) self.layout.addWidget(new_model_button) self.layout.update() def indexChanged(self, index): if self.add_none: if index == 0: self.selected_model = None else: self.selected_model = self.models[index - 1] # First item is now add else: self.selected_model = self.models[index] if self.on_select: self.on_select(self.selected_model) def getValue(self): return str(self.selected_model.id ) if self.selected_model is not None else None
class VistaListaMenuAmministratore(QWidget): def __init__(self): super(VistaListaMenuAmministratore, self).__init__() self.controller = ControlloreListaMenu() self.h_layout = QHBoxLayout() self.list_view = QListView() self.listview_model = QStandardItemModel(self.list_view) for ProdottoSingolo in self.controller.get_lista_menu(): item = QStandardItem() item.setText("{} ".format(ProdottoSingolo.prodotto) + "{}€".format(ProdottoSingolo.prezzo)) item.setEditable(False) font = item.font() font.setPointSize(18) item.setFont(font) self.listview_model.appendRow(item) self.list_view.setModel(self.listview_model) self.h_layout.addWidget(self.list_view) buttons_layout = QVBoxLayout() open_button = QPushButton("Apri") open_button.clicked.connect(self.show_selected_info) buttons_layout.addWidget(open_button) add_button = QPushButton("Inserisci") add_button.clicked.connect(self.add_info) buttons_layout.addWidget(add_button) delete_button = QPushButton("Elimina") delete_button.clicked.connect(self.delete_selected_info) buttons_layout.addWidget(delete_button) buttons_layout.addStretch() self.h_layout.addLayout(buttons_layout) self.setLayout(self.h_layout) self.resize(600, 300) self.setWindowTitle("Lista Menu") def closeEvent(self, event): self.controller.save_data() event.accept() def show_selected_info(self): if len(self.list_view.selectedIndexes()) > 0: selected = self.list_view.selectedIndexes()[0].row() prodotto_selezionato = self.controller.get_prodotto_by_index( selected) self.vista_prodotto = VistaProdotto(prodotto_selezionato) self.vista_prodotto.show() def delete_selected_info(self): if len(self.list_view.selectedIndexes()) > 0: selected = self.list_view.selectedIndexes()[0].row() prodotto_selezionato = self.controller.get_prodotto_by_index( selected) self.controller.elimina_prodotto(prodotto_selezionato) self.update_ui() self.h_layout.replaceWidget(self.list_view, self.list_view) def add_info(self): self.vista_inserisci_prodotto = VistaInserisciProdotto( self.controller, self.update_ui) self.vista_inserisci_prodotto.show() def update_ui(self): self.listview_model = QStandardItemModel(self.list_view) for prodotto in self.controller.get_lista_menu(): item = QStandardItem() item.setText(prodotto.prodotto + " {}".format(prodotto.prezzo) + "€") item.setEditable(False) font = item.font() font.setPointSize(18) item.setFont(font) self.listview_model.appendRow(item) self.list_view.setModel(self.listview_model)
class GuiWindow(QMainWindow): def __init__(self, controller, base_img_fp: str): super(GuiWindow, self).__init__() # self.app = QApplication([]) self.img_manager = ImageManager(base_img_fp) self._file_path = base_img_fp self._anim_thread = None self._anim_worker = None self.animation_widget = None self._current_chunk_size = 0 self.setWindowTitle("Don't Feel Good Inc.") self.controller = controller self.create_content() self.show() def create_content(self): self.snap_sound = PyQt5.QtMultimedia.QSound("snap_ex.wav") # Build the components first # Build a textbox for the filepath. self.outer_widget = PyQt5.QtWidgets.QWidget( self) # Dummy outer widget needed so that we can add others to it # This is the frame to hold the options. self.main_frame = PyQt5.QtWidgets.QGroupBox( self.outer_widget ) # Specifying parent=self locks it within the current window # self.main_frame.setTitle("Parameters") # The main layout for the param box and the image self.main_layout_wide = QHBoxLayout(self.main_frame) self.input_frame = QWidget(self.outer_widget) self.main_layout_wide.addWidget(self.input_frame) self.input_frame_layout = QVBoxLayout(self.input_frame) # Now, adding the parts back in... self.textbox = QLabel(self.input_frame) self.textbox.setText(self._file_path) self.load_file_button = PyQt5.QtWidgets.QPushButton(self.input_frame) self.load_file_button.setText("Browse for file") self.load_file_button.clicked.connect(self.on_load) self.gain_slider_label = PyQt5.QtWidgets.QLabel(self.input_frame) self.gain_slider_label.setText("Custom adjustment") self.gain_slider = PyQt5.QtWidgets.QSlider(Qt.Horizontal, self.input_frame) self.gain_slider.valueChanged.connect(self.on_gain_slider_adjust) # size slider self.chunk_slider_label = PyQt5.QtWidgets.QLabel(self.input_frame) self.chunk_slider_label.setText("Chunk size") self.chunk_size_slider = PyQt5.QtWidgets.QSlider( Qt.Horizontal, self.input_frame) # Start this in the middle so we don't get a div/0 self.chunk_size_slider.setSliderPosition(40) self.chunk_size_slider.valueChanged.connect( self.on_chunk_slider_adjust) # This needs to be controlled with a single button press as it'll involve reloading the object self.chunk_size_button = PyQt5.QtWidgets.QPushButton( "Apply chunk size", self.input_frame) self.chunk_size_button.clicked.connect(self.on_chunk_click) # self.go_button = PyQt5.QtWidgets.QPushButton("*snap*", self.input_frame) # # self.go_button.clicked.connect(self.on_snap) self.full_snap_button = PyQt5.QtWidgets.QPushButton( "*snap*", self.input_frame) self.full_snap_button.clicked.connect(self.display_animation) self.reset_button = PyQt5.QtWidgets.QPushButton( "Use the time stone (reset)", self.input_frame) self.reset_button.clicked.connect(self.on_reset) self.input_frame_layout.addWidget(self.textbox) self.input_frame_layout.addWidget(self.load_file_button) self.input_frame_layout.addWidget(self.chunk_slider_label) self.input_frame_layout.addWidget(self.chunk_size_slider) self.input_frame_layout.addWidget(self.chunk_size_button) self.input_frame_layout.addWidget(self.gain_slider_label) self.input_frame_layout.addWidget(self.gain_slider) # self.input_frame_layout.addWidget(self.go_button) self.input_frame_layout.addWidget(self.full_snap_button) self.input_frame_layout.addWidget(self.reset_button) # Trying something else out self.image_frame = QFrame(self) # Creating the second container self.image_widget = ImageWidget(self._file_path, self.outer_widget) self.main_layout_wide.addWidget(self.image_widget) # self.main_layout.addLayout(self.main_frame) self.setCentralWidget(self.outer_widget) self.setFixedSize(self.main_layout_wide.sizeHint()) self.on_chunk_click() # Make sure we're ready to handle an animation if the need arises # AnimationReadyEmitter.trigger.connect(self.display_animation) # self.main_frame.show() def on_load(self): dlg = QFileDialog() dlg.setFileMode(QFileDialog.ExistingFile) new_path, _ = dlg.getOpenFileName(None, "Open file", "%userprofile%\\Pictures\\") if new_path == ("", ""): return if not os.path.exists(new_path): self.textbox.setStyleSheet("color: rgb(255, 0, 0);") else: self.textbox.setStyleSheet("color: rgb(0, 0, 0);") self._file_path = new_path self.textbox.setText(self._file_path) self.img_manager = ImageManager(new_path) q_pixmap = PyQt5.QtGui.QPixmap(self._file_path) self.image_widget.setPixmap(q_pixmap) self.image_widget.resize(q_pixmap.width(), q_pixmap.height()) self.main_frame.resize(self.main_layout_wide.sizeHint()) self.setFixedSize(self.main_layout_wide.sizeHint()) def display_animation(self): self.snap_sound.play() self._anim_worker = AnimWorker(self.img_manager, True) self._anim_thread = QThread() self._anim_worker.moveToThread(self._anim_thread) self._anim_worker.anim_done.connect(self.on_animation_complete) self._anim_thread.started.connect(self._anim_worker.work) self.full_snap_button.setDisabled(True) self._anim_thread.start() @pyqtSlot(str) def on_animation_complete(self, file_path: str): """Replace the original image widget until the movie completes, and then change it back""" self.animation_widget = AnimationWidget(file_path, self.outer_widget) self.full_snap_button.setDisabled(False) # self.main_layout_wide.replaceWidget(self.image_widget, self.animation_widget) prev_image_ix = self.main_layout_wide.indexOf(self.image_widget) # self.main_layout_wide.removeWidget(self.image_widget) self.image_widget.hide() # FIXME Yuck self.main_layout_wide.addWidget(self.animation_widget, Qt.Horizontal) self.animation_widget.movie.finished.connect( self.replace_original_img_widget) # self.animation_widget.movie.setScaledSize(QSize(900, 900)) self.animation_widget.movie.start() self.animation_widget.show() @pyqtSlot() def replace_original_img_widget(self): self.animation_widget.movie.stop() # This is needed to self.animation_widget.hide() self.image_widget.show() self.main_layout_wide.removeWidget(self.animation_widget) self.main_layout_wide.insertWidget(1, self.image_widget) self.main_layout_wide.replaceWidget(self.animation_widget, self.image_widget) def on_snap(self): self.snap_sound.play() self.reload_image() def on_full_snap(self): for i in reversed(range(1, 101)): self.gain_slider.setSliderPosition(i) self.reload_image() sleep(3 / i**2) # sleep(1) def reload_image(self): """ Reload the image within the current frame, asking the underlying functions to recalculate based on its current values. """ img = self.img_manager.update_image() q_image = PyQt5.QtGui.QImage.fromData(img.read()) q_pixmap = PyQt5.QtGui.QPixmap.fromImage(q_image) self.image_widget.setPixmap(q_pixmap) def on_gain_slider_adjust(self): new_value = self.gain_slider.value() # TODO Scale this print(new_value) self.img_manager.gain = new_value if self.img_manager.chunk_size > 10: self.reload_image() def on_chunk_slider_adjust(self): if self.chunk_size_slider.value() != self._current_chunk_size: self.chunk_size_button.setDisabled(False) def on_chunk_click(self): new_value = self.chunk_size_slider.value( ) + 1 # Make sure it's never equal to 0 self.img_manager.chunk_size = new_value self.chunk_size_button.setDisabled(True) def on_reset(self): self.img_manager.reset() self.gain_slider.setValue(0) q_pixmap = PyQt5.QtGui.QPixmap(self._file_path) self.image_widget.setPixmap(q_pixmap)