def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.clip_properties_model = PropertiesModel(self) # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_item = None # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect(self.filter_changed)
def updateProperty(self, id, frame_number, property_key, new_value): """Update a keyframe property to a new value, adding or updating keyframes as needed""" found_point = False clip_updated = False c = Clip.get(id=id) if not c: # No clip found return for point in c.data[property_key]["Points"]: log.info("looping points: co.X = %s" % point["co"]["X"]) if point["co"]["X"] == frame_number: found_point = True clip_updated = True point["interpolation"] = openshot.BEZIER point["co"]["Y"] = float(new_value) if not found_point and new_value != None: clip_updated = True log.info("Created new point at X=%s" % frame_number) c.data[property_key]["Points"].append({'co': {'X': frame_number, 'Y': new_value}, 'interpolation': openshot.BEZIER}) # Reduce # of clip properties we are saving (performance boost) c.data = {property_key: c.data.get(property_key)} # Save changes if clip_updated: # Save c.save() # Update the preview get_app().window.refreshFrameSignal.emit()
def __init__(self, parent, *args): # Keep track of the selected items (clips, transitions, etc...) self.selected = [] self.current_item_id = None self.frame_number = 1 self.previous_hash = "" self.new_item = True self.items = {} self.ignore_update_signal = False self.parent = parent self.previous_filter = None self.filter_base_properties = [] # Create standard model self.model = ClipStandardItemModel() self.model.setColumnCount(2) # Timer to use a delay before showing properties (to prevent a mass selection from trying # to update the property model hundreds of times) self.update_timer = QTimer() self.update_timer.setInterval(100) self.update_timer.timeout.connect(self.update_item_timeout) self.update_timer.stop() self.next_item_id = None self.next_item_type = None # Connect data changed signal self.model.itemChanged.connect(self.value_updated) # Add self as listener to project data updates (used to update the timeline) get_app().updates.add_listener(self)
def accept(self): app = get_app() _ = app._tr # Get current project folder (if any) project_path = get_app().project.current_filepath default_folder = info.HOME_PATH if project_path: default_folder = os.path.dirname(project_path) # Init file path for new title title_path = os.path.join(default_folder, "%s.svg" % _("New Title")) # Get file path for SVG title file_path, file_type = QFileDialog.getSaveFileName(self, _("Save Title As..."), title_path, _("Scalable Vector Graphics (*.svg)")) if file_path: # Append .svg (if not already there) if not file_path.endswith("svg"): file_path = file_path + ".svg" # Update filename self.filename = file_path # Save title self.writeToFile(self.xmldoc) # Add file to project self.add_file(self.filename) # Close window super(TitleEditor, self).accept()
def bool_value_changed(self, widget, param, state): # Save setting if state == Qt.Checked: self.s.set(param["setting"], True) else: self.s.set(param["setting"], False) # Trigger specific actions if param["setting"] == "debug-mode": # Update debug setting of timeline log.info("Setting debug-mode to %s" % (state == Qt.Checked)) debug_enabled = (state == Qt.Checked) # Enable / Disable logger openshot.ZmqLogger.Instance().Enable(debug_enabled) elif param["setting"] == "enable-auto-save": # Toggle autosave if (state == Qt.Checked): # Start/Restart autosave timer get_app().window.auto_save_timer.start() else: # Stop autosave timer get_app().window.auto_save_timer.stop() # Check for restart self.check_for_restart(param)
def __init__(self, *args): # Invoke parent init QFrame.__init__(self, *args) self.item_id = None self.item_type = None # Get translation object _ = get_app()._tr # Widgets self.lblSelection = QLabel() self.lblSelection.setText("<strong>%s</strong>" % _("No Selection")) self.btnSelectionName = QPushButton() self.btnSelectionName.setVisible(False) self.btnSelectionName.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) # Support rich text self.lblSelection.setTextFormat(Qt.RichText) hbox = QHBoxLayout() hbox.setContentsMargins(0,0,0,0) hbox.addWidget(self.lblSelection) hbox.addWidget(self.btnSelectionName) self.setLayout(hbox) # Connect signals get_app().window.propertyTableView.loadProperties.connect(self.select_item)
def Action_Triggered(self, event): # Switch selection item_id = self.sender().data()['item_id'] item_type = self.sender().data()['item_type'] log.info('switch selection to %s:%s' % (item_id, item_type)) # Set the property tableview to the new item get_app().window.propertyTableView.loadProperties.emit(item_id, item_type)
def eventFilter(self, object, e): if e.type() == QEvent.WindowActivate: # Raise parent window, and then this tutorial get_app().window.showNormal() get_app().window.raise_() self.raise_() return False
def actionNew_trigger(self, event): # clear data and start new project get_app().project.load("") get_app().updates.reset() self.filesTreeView.refresh_view() log.info("New Project created.") # Set Window title self.SetWindowTitle()
def update_item_timeout(self): # Get the next item id, and type item_id = self.next_item_id item_type = self.next_item_type # Clear previous selection self.selected = [] self.filter_base_properties = [] log.info("Update item: %s" % item_type) if item_type == "clip": c = None clips = get_app().window.timeline_sync.timeline.Clips() for clip in clips: if clip.Id() == item_id: c = clip break # Append to selected clips self.selected.append((c, item_type)) if item_type == "transition": t = None trans = get_app().window.timeline_sync.timeline.Effects() for tran in trans: if tran.Id() == item_id: t = tran break # Append to selected clips self.selected.append((t, item_type)) if item_type == "effect": e = None clips = get_app().window.timeline_sync.timeline.Clips() for clip in clips: for effect in clip.Effects(): if effect.Id() == item_id: e = effect break # Filter out basic properties, since this is an effect on a clip self.filter_base_properties = ["position", "layer", "start", "end", "duration"] # Append to selected items self.selected.append((e, item_type)) # Update frame # from timeline self.update_frame(get_app().window.preview_thread.player.Position(), reload_model=False) # Get ID of item self.new_item = True # Update the model data self.update_model(get_app().window.txtPropertyFilter.text())
def mouseReleaseEvent(self, event): # Inform UpdateManager to accept updates, and only store our final update get_app().updates.ignore_history = False # Add final update to undo/redo history get_app().updates.apply_last_action_to_history(self.original_data) # Clear original data self.original_data = None
def getMenu(self): # Build menu for selection button menu = QMenu(self) # Get translation object _ = get_app()._tr # Look up item for more info if self.item_type == "clip": self.item_name = Clip.get(id=self.item_id).title() elif self.item_type == "transition": self.item_name = Transition.get(id=self.item_id).title() elif self.item_type == "effect": self.item_name = Effect.get(id=self.item_id).title() # Add selected clips for item_id in get_app().window.selected_clips: clip = Clip.get(id=item_id) item_name = clip.title() item_icon = QIcon(QPixmap(clip.data.get('image'))) action = menu.addAction(item_name) action.setIcon(item_icon) action.setData({'item_id':item_id, 'item_type':'clip'}) action.triggered.connect(self.Action_Triggered) # Add effects for these clips (if any) for effect in clip.data.get('effects'): item_name = Effect.get(id=effect.get('id')).title() item_icon = QIcon(QPixmap(os.path.join(info.PATH, "effects", "icons", "%s.png" % effect.get('class_name').lower()))) action = menu.addAction(' > %s' % _(item_name)) action.setIcon(item_icon) action.setData({'item_id': effect.get('id'), 'item_type': 'effect'}) action.triggered.connect(self.Action_Triggered) # Add selected transitions for item_id in get_app().window.selected_transitions: trans = Transition.get(id=item_id) item_name = _(trans.title()) item_icon = QIcon(QPixmap(trans.data.get('reader',{}).get('path'))) action = menu.addAction(_(item_name)) action.setIcon(item_icon) action.setData({'item_id': item_id, 'item_type': 'transition'}) action.triggered.connect(self.Action_Triggered) # Add selected effects for item_id in get_app().window.selected_effects: effect = Effect.get(id=item_id) item_name = _(effect.title()) item_icon = QIcon(QPixmap(os.path.join(info.PATH, "effects", "icons", "%s.png" % effect.data.get('class_name').lower()))) action = menu.addAction(_(item_name)) action.setIcon(item_icon) action.setData({'item_id': item_id, 'item_type': 'effect'}) action.triggered.connect(self.Action_Triggered) # Return the menu object return menu
def load(self, file_path): """ Load project from file """ self.new() if file_path: log.info("Loading project file: {}".format(file_path)) # Default project data default_project = self._data try: # Attempt to load v2.X project file project_data = self.read_from_file(file_path, path_mode="absolute") except Exception as ex: try: # Attempt to load legacy project file (v1.X version) project_data = self.read_legacy_project_file(file_path) except Exception as ex: # Project file not recognized as v1.X or v2.X, bubble up error raise ex # Merge default and project settings, excluding settings not in default. self._data = self.merge_settings(default_project, project_data) # On success, save current filepath self.current_filepath = file_path # Check if paths are all valid self.check_if_paths_are_valid() # Copy any project thumbnails to main THUMBNAILS folder loaded_project_folder = os.path.dirname(self.current_filepath) project_thumbnails_folder = os.path.join(loaded_project_folder, "thumbnail") if os.path.exists(project_thumbnails_folder): # Remove thumbnail path shutil.rmtree(info.THUMBNAIL_PATH, True) # Copy project thumbnails folder shutil.copytree(project_thumbnails_folder, info.THUMBNAIL_PATH) # Add to recent files setting self.add_to_recent_files(file_path) # Upgrade any data structures self.upgrade_project_data_structures() # Get app, and distribute all project data through update manager from classes.app import get_app get_app().updates.load(self._data) # Clear needs save flag self.has_unsaved_changes = False
def update_frame(self, frame_number, reload_model=True): # Check for a selected clip if self.selected: clip, item_type = self.selected[0] if not clip: # Ignore null clip return # If effect, find the position of the parent clip if item_type == "effect": # find parent clip effect = Effect.get(id=clip.Id()) if not effect: # Invalid effect return parent_clip_id = effect.parent["id"] # Find this clip object clips = get_app().window.timeline_sync.timeline.Clips() for c in clips: if c.Id() == parent_clip_id: # Override the selected clip object (so the effect gets the correct starting position) clip = c break # Get FPS from project fps = get_app().project.get(["fps"]) fps_float = float(fps["num"]) / float(fps["den"]) # Requested time requested_time = float(frame_number - 1) / fps_float # Determine the frame needed for this clip (based on the position on the timeline) time_diff = (requested_time - clip.Position()) + clip.Start() self.frame_number = round(time_diff * fps_float) + 1 # Calculate biggest and smallest possible frames min_frame_number = round((clip.Start() * fps_float)) + 1 max_frame_number = round((clip.End() * fps_float)) + 1 # Adjust frame number if out of range if self.frame_number < min_frame_number: self.frame_number = min_frame_number if self.frame_number > max_frame_number: self.frame_number = max_frame_number log.info("Update frame to %s" % self.frame_number) # Update the model data if reload_model: self.update_model(get_app().window.txtPropertyFilter.text())
def spinner_value_changed(self, param, value): # Save setting self.s.set(param["setting"], value) log.info(value) if param["setting"] == "autosave-interval": # Update autosave interval (# of minutes) get_app().window.auto_save_timer.setInterval(value * 1000 * 60) # Check for restart self.check_for_restart(param)
def dropdown_index_changed(self, widget, param, index): # Save setting value = widget.itemData(index) self.s.set(param["setting"], value) log.info(value) # Apply cache settings (if needed) if param["setting"] in ["cache-mode", "cache-image-format"]: get_app().window.InitCacheSettings() # Check for restart self.check_for_restart(param)
def bool_value_changed(self, widget, param, state): # Save setting if state == Qt.Checked: self.s.set(param["setting"], True) else: self.s.set(param["setting"], False) # Trigger specific actions if param["setting"] == "debug-mode": # Update debug setting of timeline log.info("Setting debug-mode to %s" % (state == Qt.Checked)) debug_enabled = (state == Qt.Checked) # Enable / Disable logger openshot.ZmqLogger.Instance().Enable(debug_enabled) elif param["setting"] == "enable-auto-save": # Toggle autosave if (state == Qt.Checked): # Start/Restart autosave timer get_app().window.auto_save_timer.start() else: # Stop autosave timer get_app().window.auto_save_timer.stop() elif param["setting"] == "hardware_decode": if (state == Qt.Checked): # Enable hardware decode openshot.Settings.Instance().HARDWARE_DECODE = True else: # Disable hardware decode openshot.Settings.Instance().HARDWARE_DECODE = False elif param["setting"] == "hardware_encode": if (state == Qt.Checked): # Enable hardware encode openshot.Settings.Instance().HARDWARE_ENCODE = True else: # Disable hardware encode openshot.Settings.Instance().HARDWARE_ENCODE = False elif param["setting"] == "omp_threads_enabled": if (state == Qt.Checked): # Enable OMP multi-threading openshot.Settings.Instance().WAIT_FOR_VIDEO_PROCESSING_TASK = False else: # Disable OMP multi-threading openshot.Settings.Instance().WAIT_FOR_VIDEO_PROCESSING_TASK = True # Check for restart self.check_for_restart(param)
def select_item(self, item_id, item_type): """ Update the selected item in the properties window """ # Get translation object _ = get_app()._tr # Set label if item_id: get_app().window.lblSelectedItem.setText(_("Selection: %s") % item_id) else: get_app().window.lblSelectedItem.setText(_("No Selection")) # Update item self.clip_properties_model.update_item(item_id, item_type)
def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.clip_properties_model = PropertiesModel(self) self.transition_model = TransitionsModel(self) self.files_model = FilesModel(self) # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_label = None self.selected_item = None self.new_value = None self.original_data = None # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Set delegate delegate = PropertyDelegate() self.setItemDelegateForColumn(1, delegate) self.previous_x = -1 # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() self.transition_model.update_model() self.files_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect(self.filter_changed) get_app().window.InsertKeyframe.connect(self.Insert_Action_Triggered) self.doubleClicked.connect(self.doubleClickedCB) self.loadProperties.connect(self.select_item)
def btnBrowse_clicked(self): log.info("btnBrowse_clicked") # get translations app = get_app() _ = app._tr # update export folder path file_path = QFileDialog.getExistingDirectory(self, _("Choose a Folder..."), self.txtExportFolder.text()) if os.path.exists(file_path): self.txtExportFolder.setText(file_path) # update export folder path in project file get_app().updates.update(["export_path"], file_path)
def spinner_value_changed(self, param, value): # Save setting self.s.set(param["setting"], value) log.info(value) if param["setting"] == "autosave-interval": # Update autosave interval (# of minutes) get_app().window.auto_save_timer.setInterval(value * 1000 * 60) # Apply cache settings (if needed) if param["setting"] in ["cache-limit-mb", "cache-scale", "cache-quality"]: get_app().window.InitCacheSettings() # Check for restart self.check_for_restart(param)
def mouseReleaseEvent(self, event): """Capture mouse release event on video preview window""" self.mouse_pressed = False self.mouse_dragging = False self.transform_mode = None # Inform UpdateManager to accept updates, and only store our final update get_app().updates.ignore_history = False # Add final update to undo/redo history if self.original_clip_data: get_app().updates.apply_last_action_to_history(self.original_clip_data) # Clear original data self.original_clip_data = None
def cboProfile_index_changed(self, widget, index): selected_profile_path = widget.itemData(index) log.info(selected_profile_path) # get translations app = get_app() _ = app._tr # Load profile profile = openshot.Profile(selected_profile_path) # Load profile settings into advanced editor self.txtWidth.setValue(profile.info.width) self.txtHeight.setValue(profile.info.height) self.txtFrameRateNum.setValue(profile.info.fps.num) self.txtFrameRateDen.setValue(profile.info.fps.den) self.txtAspectRatioNum.setValue(profile.info.display_ratio.num) self.txtAspectRatioDen.setValue(profile.info.display_ratio.den) self.txtPixelRatioNum.setValue(profile.info.pixel_ratio.num) self.txtPixelRatioDen.setValue(profile.info.pixel_ratio.den) # Load the interlaced options self.cboInterlaced.clear() self.cboInterlaced.addItem(_("Yes"), "Yes") self.cboInterlaced.addItem(_("No"), "No") if profile.info.interlaced_frame: self.cboInterlaced.setCurrentIndex(0) else: self.cboInterlaced.setCurrentIndex(1)
def cboSimpleProjectType_index_changed(self, widget, index): selected_project = widget.itemData(index) # set the target dropdown based on the selected project type # first clear the combo self.cboSimpleTarget.clear() # get translations app = get_app() _ = app._tr # parse the xml files and get targets that match the project type project_types = [] for file in os.listdir(info.EXPORT_PRESETS_DIR): xmldoc = xml.parse(os.path.join(info.EXPORT_PRESETS_DIR, file)) type = xmldoc.getElementsByTagName("type") if _(type[0].childNodes[0].data) == selected_project: titles = xmldoc.getElementsByTagName("title") for title in titles: project_types.append(_(title.childNodes[0].data)) # Add all targets for selected project type for item in sorted(project_types): self.cboSimpleTarget.addItem(item, item)
def contextMenuEvent(self, event): # Update selection self.updateSelection() # Set context menu mode app = get_app() app.context_menu_object = "files" menu = QMenu(self) menu.addAction(self.win.actionImportFiles) menu.addSeparator() if self.selected: # If file selected, show file related options menu.addAction(self.win.actionPreview_File) menu.addAction(self.win.actionSplitClip) menu.addAction(self.win.actionAdd_to_Timeline) menu.addSeparator() #menu.addAction(self.win.actionFile_Properties) menu.addAction(self.win.actionRemove_from_Project) menu.addSeparator() menu.addAction(self.win.actionThumbnailView) # Show menu menu.exec_(QCursor.pos())
def __init__(self): # Create dialog class QDialog.__init__(self) # Load UI from designer ui_util.load_ui(self, self.ui_path) # Init Ui ui_util.init_ui(self) # get translations self.app = get_app() _ = self.app._tr # Init license #license_path = os.path.join(path_xdg, 'COPYING') #license_path = os.path.join(info.PATH, 'COPYING') #my_license = open('license_path', "r") #content = my_license.read() #for text in license_path: #self.textBrowser.append(text) #self.textBrowser.append(content) with open("(os.path.join(info.PATH, 'COPYING'))", 'r') as my_license: text = my_license.read() self.textBrowser.append(text)
def get_thumb_path( self, file_id, thumbnail_frame, clear_cache=False): """Get thumbnail path by invoking HTTP thumbnail request""" # Clear thumb cache (if requested) thumb_cache = "" if clear_cache: thumb_cache = "no-cache/" # Connect to thumbnail server and get image thumb_server_details = get_app().window.http_server_thread.server_address thumb_address = "http://%s:%s/thumbnails/%s/%s/path/%s" % ( thumb_server_details[0], thumb_server_details[1], file_id, thumbnail_frame, thumb_cache) r = get(thumb_address) if r.ok: # Update thumbnail path to real one return r.text else: return ''
def filterAcceptsRow(self, sourceRow, sourceParent): """Filter for common transitions and text filter""" if get_app().window.actionTransitionsShowCommon.isChecked(): # Fetch the group name index = self.sourceModel().index(sourceRow, 2, sourceParent) # group name column group_name = self.sourceModel().data( index) # group name (i.e. common) # Fetch the transitions name index = self.sourceModel().index( sourceRow, 0, sourceParent) # transition name column trans_name = self.sourceModel().data( index) # transition name (i.e. Fade In) # Return, if regExp match in displayed format. return group_name == "common" and self.filterRegExp().indexIn( trans_name) >= 0 # Continue running built-in parent filter logic return super(TransitionFilterProxyModel, self).filterAcceptsRow(sourceRow, sourceParent)
def __init__(self): # Create dialog class QDialog.__init__(self) # Load UI from designer ui_util.load_ui(self, self.ui_path) # Init Ui ui_util.init_ui(self) # get translations self.app = get_app() _ = self.app._tr # Init license with open(os.path.join(info.RESOURCES_PATH, 'license.txt'), 'r') as my_license: text = my_license.read() self.textBrowser.append(text) # Scroll to top cursor = self.textBrowser.textCursor() cursor.setPosition(0) self.textBrowser.setTextCursor(cursor)
def __init__(self, *args): # Invoke parent init QListView.__init__(self, *args) # Get a reference to the window object self.app = get_app() self.win = args[0] # Get Model data self.title_model = TitlesModel() # Setup header columns self.setModel(self.title_model.model) self.setIconSize(QSize(131, 108)) self.setGridSize(QSize(102, 92)) self.setViewMode(QListView.IconMode) self.setResizeMode(QListView.Adjust) self.setUniformItemSizes(True) self.setWordWrap(True) self.setTextElideMode(Qt.ElideRight) # Refresh view self.refresh_view()
def __init__(self): # Create dialog class QDialog.__init__(self) # Load UI from designer ui_util.load_ui(self, self.ui_path) # Init Ui ui_util.init_ui(self) # get translations self.app = get_app() _ = self.app._tr #create_text = _('Your X-Factor for composing amazing videos') description_text = _( 'Magic VideoX Pro is simple, intuitive and amazing video editing software. Your X-Factor for composing amazing videos' ) #learnmore_text = _('Learn more') #copyright_text = _('Copyright © %(begin_year)s-%(current_year)s') % {'begin_year': '2008', 'current_year': str(datetime.datetime.today().year) } #about_html = '<html><head/><body><hr/><p align="center"><span style=" font-size:10pt; font-weight:600;">%s</span></p><p align="center"><span style=" font-size:10pt;">%s </span><a href="http://%s.openshot.org?r=about-us"><span style=" font-size:10pt; text-decoration: none; color:#55aaff;">%s</span></a><span style=" font-size:10pt;">.</span></p></body></html>' % (create_text, description_text, info.website_language(), learnmore_text) #company_html = '<html><head/><body style="font-size:11pt; font-weight:400; font-style:normal;">\n<hr />\n<p align="center" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">%s </span><a href="http://%s.openshotstudios.com?r=about-us"><span style=" font-size:10pt; font-weight:600; text-decoration: none; color:#55aaff;">OpenShot Studios, LLC<br /></span></a></p></body></html>' % (copyright_text, info.website_language()) # Set description and company labels self.lblAboutDescription.setText(description_text) #self.lblAboutCompany.setText(company_html) # set events handlers #self.btncredit.clicked.connect(self.load_credit) #self.btnlicense.clicked.connect(self.load_license) # Init some variables #self.txtversion.setText(_("Version: %s") % info.VERSION) self.txtversion.setAlignment(Qt.AlignCenter) # Track metrics track_metric_screen("about-screen")
def __init__(self, *args): # Init QObject superclass super().__init__(*args) # Create standard model self.app = get_app() self.model = QStandardItemModel() self.model.setColumnCount(4) self.model_paths = {} # Create proxy model (for sorting and filtering) self.proxy_model = TransitionFilterProxyModel() self.proxy_model.setDynamicSortFilter(True) self.proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive) self.proxy_model.setSortCaseSensitivity(Qt.CaseSensitive) self.proxy_model.setSourceModel(self.model) self.proxy_model.setSortLocaleAware(True) # Create selection model to share between views self.selection_model = QItemSelectionModel(self.proxy_model) # Attempt to load model testing interface, if requested # (will only succeed with Qt 5.11+) if info.MODEL_TEST: try: # Create model tester objects from PyQt5.QtTest import QAbstractItemModelTester self.model_tests = [] for m in [self.proxy_model, self.model]: self.model_tests.append( QAbstractItemModelTester( m, QAbstractItemModelTester.FailureReportingMode. Warning)) log.info("Enabled {} model tests for transition data".format( len(self.model_tests))) except ImportError: pass
def accept(self): app = get_app() _ = app._tr # If editing file, just update the existing file if self.edit_file_path and not self.duplicate: # Update filename self.filename = self.edit_file_path # Overwrite title svg file self.writeToFile(self.xmldoc) else: # Create new title (with unique name) file_name = "%s.svg" % self.txtFileName.toPlainText().strip() file_path = os.path.join(info.TITLE_PATH, file_name) if self.txtFileName.toPlainText().strip(): # Do we have unsaved changes? if os.path.exists(file_path) and not self.edit_file_path: ret = QMessageBox.question(self, _("Title Editor"), _("%s already exists.\nDo you want to replace it?") % file_name, QMessageBox.No | QMessageBox.Yes) if ret == QMessageBox.No: # Do nothing return # Update filename self.filename = file_path # Save title self.writeToFile(self.xmldoc) # Add file to project self.add_file(self.filename) # Close window super(TitleEditor, self).accept()
def cboSimpleProjectType_index_changed(self, widget, index): selected_project = widget.itemData(index) # set the target dropdown based on the selected project type # first clear the combo self.cboSimpleTarget.clear() # get translations app = get_app() _ = app._tr # parse the xml files and get targets that match the project type project_types = [] for preset_path in [info.EXPORT_PRESETS_PATH, info.USER_PRESETS_PATH]: for file in os.listdir(preset_path): xmldoc = xml.parse(os.path.join(preset_path, file)) type = xmldoc.getElementsByTagName("type") if _(type[0].childNodes[0].data) == selected_project: titles = xmldoc.getElementsByTagName("title") for title in titles: project_types.append(_(title.childNodes[0].data)) # Add all targets for selected project type preset_index = 0 selected_preset = 0 for item in sorted(project_types): self.cboSimpleTarget.addItem(item, item) # Find index of MP4/H.264 if item == _("MP4 (h.264)"): selected_preset = preset_index preset_index += 1 # Select MP4/H.264 as default self.cboSimpleTarget.setCurrentIndex(selected_preset)
def add_file(self, filepath): filename = os.path.basename(filepath) # Add file into project _ = get_app()._tr # Check for this path in our existing project data file = File.get(path=filepath) # If this file is already found, exit if file: return # Load filepath in libopenshot clip object (which will try multiple readers to open it) clip = openshot.Clip(filepath) # Get the JSON for the clip's internal reader try: reader = clip.Reader() file_data = json.loads(reader.Json()) # Set media type file_data["media_type"] = "image" # Save new file to the project data file = File() file.data = file_data file.save() return True except Exception as ex: # Handle exception log.error('Could not import {}: {}'.format(filename, str(ex))) msg = QMessageBox() msg.setText(_("{} is not a valid video, audio, or image file.".format(filename))) msg.exec_() return False
def btnEnd_clicked(self): """End of clip button was clicked""" _ = get_app()._tr # Pause video self.btnPlay_clicked(force="pause") # Get the current frame current_frame = self.sliderVideo.value() # Check if ending frame greater than start frame if current_frame <= self.start_frame: # Handle exception msg = QMessageBox() msg.setText( _("Please choose valid 'start' and 'end' values for your clip." )) msg.exec_() return # remember frame # self.end_frame = current_frame # Save thumbnail image self.end_image = os.path.join(info.USER_PATH, 'thumbnail', '%s.png' % self.end_frame) self.r.GetFrame(self.end_frame).Thumbnail(self.end_image, 160, 90, '', '', '#000000', True) # Set CSS on button self.btnEnd.setStyleSheet('background-image: url(%s);' % self.end_image.replace('\\', '/')) # Enable create button self.btnAddClip.setEnabled(True) log.info('btnEnd_clicked, current frame: %s' % self.end_frame)
def __init__(self, window): self.app = get_app() # window 是我 timeline_sync 的爸爸 self.window = window project = self.app.project s = settings.get_settings() # Get some settings from the project fps = project.get("fps") width = project.get("width") height = project.get("height") sample_rate = project.get("sample_rate") channels = project.get("channels") channel_layout = project.get("channel_layout") # Create an instance of a libopenshot Timeline object self.timeline = openshot.Timeline( width, height, openshot.Fraction(fps["num"], fps["den"]), sample_rate, channels, channel_layout) self.timeline.info.channel_layout = channel_layout self.timeline.info.has_audio = True self.timeline.info.has_video = True self.timeline.info.video_length = 99999 self.timeline.info.duration = 999.99 self.timeline.info.sample_rate = sample_rate self.timeline.info.channels = channels # Open the timeline reader self.timeline.Open() # Add self as listener to project data updates (at the beginning of the list) # This listener will receive events before others. self.app.updates.add_listener(self, 0) # Connect to signal self.window.MaxSizeChanged.connect(self.MaxSizeChangedCB)
def __init__(self, *args): # Invoke parent init QWidget.__init__(self, *args) # Init aspect ratio settings (default values) self.aspect_ratio = openshot.Fraction() self.pixel_ratio = openshot.Fraction() self.aspect_ratio.num = 16 self.aspect_ratio.den = 9 self.pixel_ratio.num = 1 self.pixel_ratio.den = 1 # Init Qt style properties (black background, ect...) p = QPalette() p.setColor(QPalette.Window, Qt.black) super().setPalette(p) super().setAttribute(Qt.WA_OpaquePaintEvent) super().setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) # Init current frame's QImage self.current_image = None # Get a reference to the window object self.win = get_app().window
def btnFont_clicked(self): app = get_app() _ = app._tr # Default to previously-selected font oldfont = self.qfont # Get font from user font, ok = QFontDialog.getFont(oldfont, caption=("Change Font")) # Update SVG font if ok and font is not oldfont: self.qfont = font fontinfo = QtGui.QFontInfo(font) self.font_family = fontinfo.family() self.font_style = fontinfo.styleName() self.font_weight = fontinfo.weight() self.set_font_style() # Something changed, so update temp SVG self.writeToFile(self.xmldoc) # Display SVG again self.display_svg()
def currentChanged(self, selected, deselected): # Get selected item self.selected = selected self.deselected = deselected # Get translation object _ = get_app()._tr # Get all selected rows items if self.title_model.model.itemFromIndex(self.selected): ItemRow = self.title_model.model.itemFromIndex(self.selected).row() title_path = self.title_model.model.item(ItemRow, 2).text() # Display title in graphicsView self.win.filename = title_path # Create temp version of title self.win.create_temp_title(title_path) # Add all widgets for editing self.win.load_svg_template() # Display temp image (slight delay to allow screen to be shown first) QTimer.singleShot(50, self.win.display_svg)
def hide_tips(self, tid, user_clicked=False): """ Hide the current tip, and don't show anymore """ s = get_app().get_settings() # Loop through and find current tid for tutorial_object in self.tutorial_objects: # Get details tutorial_id = tutorial_object["id"] if tutorial_id == tid: # Hide dialog self.close_dialogs() # Update settings that this tutorial is completed if tid not in self.tutorial_ids: self.tutorial_ids.append(str(tid)) s.set("tutorial_ids", ",".join(self.tutorial_ids)) # Mark tutorial as completed (if settings) if user_clicked: # Disable all tutorials self.tutorial_enabled = False s.set("tutorial_enabled", False) # Forgot current tutorial self.current_dialog = None
def __init__(self): # Create dialog class QDialog.__init__(self) # Load UI from designer ui_util.load_ui(self, self.ui_path) # Init Ui ui_util.init_ui(self) # get translations self.app = get_app() _ = self.app._tr # set events handlers self.btncredit.clicked.connect(self.load_credit) self.btnlicense.clicked.connect(self.load_license) # Init some variables self.txtversion.setText(_("Version: %s") % info.VERSION) self.txtversion.setAlignment(Qt.AlignCenter) # Track metrics track_metric_screen("about-screen")
def addTrack(self, name): log.info("add track %s", name) # Get # of tracks all_tracks = get_app().project.get(["layers"]) track_number = 1000000 if len(list(reversed(sorted(all_tracks, key=itemgetter('number'))))) > 0: track_number = list( reversed(sorted( all_tracks, key=itemgetter('number'))))[0].get("number") + 1000000 # Create new track above existing layer(s) track = Track() track.data = { "number": track_number, id: str(len(all_tracks)), "y": 0, "label": "", "lock": False, "name": name } track.save() return track
def value_updated(self, item): """ Name or tags updated """ if self.files_model.ignore_updates: return # Get translation method _ = get_app()._tr # Determine what was changed file_id = self.files_model.model.item(item.row(), 5).text() name = self.files_model.model.item(item.row(), 1).text() tags = self.files_model.model.item(item.row(), 2).text() # Get file object and update friendly name and tags attribute f = File.get(id=file_id) f.data.update({"name": name or os.path.basename(f.data.get("path"))}) if "tags" in f.data or tags: f.data.update({"tags": tags}) # Save File f.save() # Update file thumbnail self.win.FileUpdated.emit(file_id)
def updateTotal(self): """Calculate the total length of what's about to be added to the timeline""" fade_value = self.cmbFade.currentData() fade_length = self.txtFadeLength.value() transition_path = self.cmbTransition.currentData() transition_length = self.txtTransitionLength.value() total = 0.0 for file in self.treeFiles.timeline_model.files: # Adjust clip duration, start, and end duration = file.data["duration"] if file.data["media_type"] == "image": duration = self.txtImageLength.value() if total != 0.0: # Don't subtract time from initial clip if not transition_path: # No transitions if fade_value != None: # Fade clip - subtract the fade length duration -= fade_length else: # Transition duration -= transition_length # Append duration to total total += duration # Get frames per second fps = get_app().project.get(["fps"]) # Update label total_parts = self.secondsToTime(total, fps["num"], fps["den"]) timestamp = "%s:%s:%s:%s" % (total_parts["hour"], total_parts["min"], total_parts["sec"], total_parts["frame"]) self.lblTotalLengthValue.setText(timestamp)
def transformTriggered(self, clip_id): """Handle the transform signal when it's emitted""" # Disable Transform UI if self.transforming_clip: # Is this the same clip_id already being transformed? if clip_id == self.transforming_clip.id: # Clear transform self.transforming_clip = None # Get new clip for transform self.transforming_clip = Clip.get(id=clip_id) if self.transforming_clip: self.transforming_clip_object = None clips = get_app().window.timeline_sync.timeline.Clips() for clip in clips: if clip.Id() == self.transforming_clip.id: self.transforming_clip_object = clip break # Update the preview and reselct current frame in properties get_app().window.refreshFrameSignal.emit() get_app().window.propertyTableView.select_frame( get_app().window.preview_thread.player.Position())
def Render(self, blend_file_path, target_script, preview_mode=False): """ Worker's Render method which invokes the Blender rendering commands """ log.info("QThread Render Method Invoked") # Init regex expression used to determine blender's render progress s = settings.get_settings() # get the blender executable path self.blender_exec_path = s.get("blender_command") self.blender_frame_expression = re.compile( r"Fra:([0-9,]*).*Mem:(.*?) .*Part ([0-9,]*)-([0-9,]*)") self.blender_saved_expression = re.compile(r"Saved: '(.*.png)(.*)'") self.blender_version = re.compile(r"Blender (.*?) ") self.blend_file_path = blend_file_path self.target_script = target_script self.preview_mode = preview_mode self.frame_detected = False self.version = None self.command_output = "" self.process = None self.is_running = True _ = get_app()._tr try: # Shell the blender command to create the image sequence command_get_version = [self.blender_exec_path, '-v'] command_render = [ self.blender_exec_path, '-b', self.blend_file_path, '-P', self.target_script ] self.process = subprocess.Popen(command_get_version, stdout=subprocess.PIPE) # Check the version of Blender self.version = self.blender_version.findall( str(self.process.stdout.readline())) if self.version: if float(self.version[0]) < 2.78: # change cursor to "default" and stop running blender command self.is_running = False # Wrong version of Blender. Must be 2.62+: self.blender_version_error.emit(float(self.version[0])) return # debug info log.info("Blender command: {} {} '{}' {} '{}'".format( command_render[0], command_render[1], command_render[2], command_render[3], command_render[4])) # Run real command to render Blender project self.process = subprocess.Popen(command_render, stdout=subprocess.PIPE) except: # Error running command. Most likely the blender executable path in the settings # is not correct, or is not the correct version of Blender (i.e. 2.62+) self.is_running = False self.blender_error_nodata.emit() return while self.is_running and self.process.poll() is None: # Look for progress info in the Blender Output line = str(self.process.stdout.readline()) self.command_output = self.command_output + line + "\n" # append all output into a variable output_frame = self.blender_frame_expression.findall(line) # Does it have a match? if output_frame: # Yes, we have a match self.frame_detected = True current_frame = output_frame[0][0] memory = output_frame[0][1] current_part = output_frame[0][2] max_parts = output_frame[0][3] # Update progress bar if not self.preview_mode: # only update progress if in 'render' mode self.progress.emit(float(current_frame), float(current_part), float(max_parts)) # Look for progress info in the Blender Output output_saved = self.blender_saved_expression.findall(str(line)) log.info("Image detected from blender regex: %s" % output_saved) # Does it have a match? if output_saved: # Yes, we have a match self.frame_detected = True image_path = output_saved[0][0] time_saved = output_saved[0][1] # Update preview image self.image_updated.emit(image_path) # Re-enable the interface self.enable_interface.emit() # Check if NO FRAMES are detected if not self.frame_detected: # Show Error that no frames are detected. This is likely caused by # the wrong command being executed... or an error in Blender. self.blender_error_with_data.emit( _("No frame was found in the output from Blender")) # Done with render (i.e. close window) elif not self.preview_mode: # only close window if in 'render' mode self.finished.emit() # Thread finished log.info("Blender render thread finished") if self.is_running == False: # close window if thread was killed self.closed.emit() # mark thread as finished self.is_running = False
def __init__(self, cuts_json, clips_json, preview=False): _ = get_app()._tr # Create dialog class QDialog.__init__(self) # Load UI from designer ui_util.load_ui(self, self.ui_path) # Init UI ui_util.init_ui(self) # Track metrics track_metric_screen("cutting-screen") # If preview, hide cutting controls if preview: self.lblInstructions.setVisible(False) self.widgetControls.setVisible(False) self.setWindowTitle(_("Preview")) self.start_frame = 1 self.start_image = None self.end_frame = 1 self.end_image = None project = get_app().project # Keep track of file object #self.file = file self.file_path = file.absolute_path() self.video_length = int(file.data['video_length']) self.fps_num = int(file.data['fps']['num']) self.fps_den = int(file.data['fps']['den']) self.fps = float(self.fps_num) / float(self.fps_den) self.width = int(file.data['width']) self.height = int(file.data['height']) self.sample_rate = int(file.data['sample_rate']) self.channels = int(file.data['channels']) self.channel_layout = int(file.data['channel_layout']) # Open video file with Reader log.info(self.file_path) # Create an instance of a libopenshot Timeline object self.r = openshot.Timeline( self.width, self.height, openshot.Fraction(self.fps_num, self.fps_den), self.sample_rate, self.channels, self.channel_layout) self.r.info.channel_layout = self.channel_layout try: # Add clip for current preview file self.clip = openshot.Clip(self.file_path) # Show waveform for audio files if not self.clip.Reader().info.has_video and self.clip.Reader( ).info.has_audio: self.clip.Waveform(True) # Set has_audio property self.r.info.has_audio = self.clip.Reader().info.has_audio if preview: # Display frame #'s during preview self.clip.display = openshot.FRAME_DISPLAY_CLIP self.r.AddClip(self.clip) except: log.error('Failed to load media file into preview player: %s' % self.file_path) return # Add Video Widget self.videoPreview = VideoWidget() self.videoPreview.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.verticalLayout.insertWidget(0, self.videoPreview) # Set max size of video preview (for speed) viewport_rect = self.videoPreview.centeredViewport( self.videoPreview.width(), self.videoPreview.height()) self.r.SetMaxSize(viewport_rect.width(), viewport_rect.height()) # Open reader self.r.Open() # Start the preview thread self.initialized = False self.transforming_clip = False self.preview_parent = PreviewParent() self.preview_parent.Init(self, self.r, self.videoPreview) self.preview_thread = self.preview_parent.worker # Set slider constraints self.sliderIgnoreSignal = False self.sliderVideo.setMinimum(1) self.sliderVideo.setMaximum(self.video_length) self.sliderVideo.setSingleStep(1) self.sliderVideo.setSingleStep(1) self.sliderVideo.setPageStep(24) # Determine if a start or end attribute is in this file start_frame = 1 if 'start' in self.file.data.keys(): start_frame = (float(self.file.data['start']) * self.fps) + 1 # Display start frame (and then the previous frame) QTimer.singleShot( 500, functools.partial(self.sliderVideo.setValue, start_frame + 1)) QTimer.singleShot( 600, functools.partial(self.sliderVideo.setValue, start_frame)) # Connect signals self.actionPlay.triggered.connect(self.actionPlay_Triggered) self.btnPlay.clicked.connect(self.btnPlay_clicked) self.sliderVideo.valueChanged.connect(self.sliderVideo_valueChanged) self.btnStart.clicked.connect(self.btnStart_clicked) self.btnEnd.clicked.connect(self.btnEnd_clicked) self.btnClear.clicked.connect(self.btnClear_clicked) self.btnAddClip.clicked.connect(self.btnAddClip_clicked) self.initialized = True
def value_updated(self, item, interpolation=-1, value=None, interpolation_details=[]): """ Table cell change event - also handles context menu to update interpolation value """ if self.ignore_update_signal: return # Get translation method _ = get_app()._tr # Determine what was changed property = self.model.item(item.row(), 0).data() property_name = property[1]["name"] closest_point_x = property[1]["closest_point_x"] previous_point_x = property[1]["previous_point_x"] property_type = property[1]["type"] property_key = property[0] clip_id, item_type = item.data() # Get value (if any) if item.text(): # Set and format value based on property type if value != None: # Override value new_value = value elif property_type == "string": # Use string value new_value = item.text() elif property_type == "bool": # Use boolean value if item.text() == _("False"): new_value = False else: new_value = True elif property_type == "int": # Use int value new_value = QLocale().system().toInt(item.text())[0] else: # Use decimal value new_value = QLocale().system().toFloat(item.text())[0] else: new_value = None log.info( "%s for %s changed to %s at frame %s with interpolation: %s at closest x: %s" % (property_key, clip_id, new_value, self.frame_number, interpolation, closest_point_x)) # Find this clip c = None clip_updated = False if item_type == "clip": # Get clip object c = Clip.get(id=clip_id) elif item_type == "transition": # Get transition object c = Transition.get(id=clip_id) elif item_type == "effect": # Get effect object c = Effect.get(id=clip_id) if c: # Update clip attribute if property_key in c.data: log.info("value updated: %s" % c.data) # Check the type of property (some are keyframe, and some are not) if type(c.data[property_key]) == dict: # Keyframe # Loop through points, find a matching points on this frame found_point = False point_to_delete = None for point in c.data[property_key]["Points"]: log.info("looping points: co.X = %s" % point["co"]["X"]) if interpolation == -1 and point["co"][ "X"] == self.frame_number: # Found point, Update value found_point = True clip_updated = True # Update or delete point if new_value != None: point["co"]["Y"] = float(new_value) log.info( "updating point: co.X = %s to value: %s" % (point["co"]["X"], float(new_value))) else: point_to_delete = point break elif interpolation > -1 and point["co"][ "X"] == previous_point_x: # Only update interpolation type (and the LEFT side of the curve) found_point = True clip_updated = True point["interpolation"] = interpolation if interpolation == 0: point["handle_right"] = point.get( "handle_right") or { "Y": 0.0, "X": 0.0 } point["handle_right"][ "X"] = interpolation_details[0] point["handle_right"][ "Y"] = interpolation_details[1] log.info( "updating interpolation mode point: co.X = %s to %s" % (point["co"]["X"], interpolation)) log.info("use interpolation preset: %s" % str(interpolation_details)) elif interpolation > -1 and point["co"][ "X"] == closest_point_x: # Only update interpolation type (and the RIGHT side of the curve) found_point = True clip_updated = True point["interpolation"] = interpolation if interpolation == 0: point["handle_left"] = point.get( "handle_left") or { "Y": 0.0, "X": 0.0 } point["handle_left"][ "X"] = interpolation_details[2] point["handle_left"][ "Y"] = interpolation_details[3] log.info( "updating interpolation mode point: co.X = %s to %s" % (point["co"]["X"], interpolation)) log.info("use interpolation preset: %s" % str(interpolation_details)) # Delete point (if needed) if point_to_delete: clip_updated = True log.info("Found point to delete at X=%s" % point_to_delete["co"]["X"]) c.data[property_key]["Points"].remove(point_to_delete) # Create new point (if needed) elif not found_point and new_value != None: clip_updated = True log.info("Created new point at X=%s" % self.frame_number) c.data[property_key]["Points"].append({ 'co': { 'X': self.frame_number, 'Y': new_value }, 'interpolation': 1 }) elif property_type == "int": # Integer clip_updated = True c.data[property_key] = int(new_value) elif property_type == "float": # Float clip_updated = True c.data[property_key] = new_value elif property_type == "bool": # Boolean clip_updated = True c.data[property_key] = bool(new_value) elif property_type == "string": # String clip_updated = True c.data[property_key] = str(new_value) # Reduce # of clip properties we are saving (performance boost) c.data = {property_key: c.data.get(property_key)} # Save changes if clip_updated: # Save c.save() # Update the preview get_app().window.refreshFrameSignal.emit() # Clear selection self.parent.clearSelection()
def update_model(self, filter=""): log.info("updating clip properties model.") app = get_app() _ = app._tr # Stop QTimer self.update_timer.stop() # Check for a selected clip if self.selected and self.selected[0]: c, item_type = self.selected[0] # Skip blank clips # TODO: Determine why c is occasional = None if not c: return # Get raw unordered JSON properties raw_properties = json.loads(c.PropertiesJSON(self.frame_number)) all_properties = OrderedDict( sorted(raw_properties.items(), key=lambda x: x[1]['name'])) log.info("Getting properties for frame %s: %s" % (self.frame_number, str(all_properties))) # Check if filter was changed (if so, wipe previous model data) if self.previous_filter != filter: self.previous_filter = filter self.new_item = True # filter changed, so we need to regenerate the entire model # Ignore any events from this method self.ignore_update_signal = True # Clear previous model data (if item is different) if self.new_item: # Prepare for new properties self.items = {} self.model.clear() # Add Headers self.model.setHorizontalHeaderLabels( [_("Property"), _("Value")]) # Loop through properties, and build a model for property in all_properties.items(): label = property[1]["name"] name = property[0] value = property[1]["value"] type = property[1]["type"] memo = property[1]["memo"] readonly = property[1]["readonly"] keyframe = property[1]["keyframe"] points = property[1]["points"] interpolation = property[1]["interpolation"] closest_point_x = property[1]["closest_point_x"] choices = property[1]["choices"] # Adding Transparency to translation file transparency_label = _("Transparency") selected_choice = None if choices: selected_choice = [ c for c in choices if c["selected"] == True ][0]["name"] # Hide filtered out properties if filter and filter.lower() not in name.lower(): continue # Hide unused base properties (if any) if name in self.filter_base_properties: continue # Insert new data into model, or update existing values row = [] if self.new_item: # Append Property Name col = QStandardItem("Property") col.setText(_(label)) col.setData(property) if keyframe and points > 1: col.setBackground( QColor("green")) # Highlight keyframe background elif points > 1: col.setBackground(QColor( 42, 130, 218)) # Highlight interpolated value background if readonly: col.setFlags(Qt.ItemIsEnabled) else: col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) row.append(col) # Append Value col = QStandardItem("Value") if selected_choice: col.setText(_(selected_choice)) elif type == "string": # Use string value col.setText(memo) elif type == "bool": # Use boolean value if value: col.setText(_("True")) else: col.setText(_("False")) elif type == "color": # Don't output a value for colors col.setText("") elif type == "int": col.setText("%d" % value) else: # Use numeric value col.setText(QLocale().system().toString(float(value), "f", precision=2)) col.setData((c.Id(), item_type)) if points > 1: # Apply icon to cell my_icon = QPixmap( os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % interpolation)) col.setData(my_icon, Qt.DecorationRole) # Set the background color of the cell if keyframe: col.setBackground(QColor( "green")) # Highlight keyframe background else: col.setBackground( QColor(42, 130, 218) ) # Highlight interpolated value background if type == "color": # Color needs to be handled special red = property[1]["red"]["value"] green = property[1]["green"]["value"] blue = property[1]["blue"]["value"] col.setBackground(QColor(red, green, blue)) if readonly or type == "color" or choices: col.setFlags(Qt.ItemIsEnabled) else: col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsEditable) row.append(col) # Append ROW to MODEL (if does not already exist in model) self.model.appendRow(row) else: # Update the value of the existing model # Get 1st Column col = self.items[name]["row"][0] col.setData(property) # For non-color types, update the background color if keyframe and points > 1: col.setBackground( QColor("green")) # Highlight keyframe background elif points > 1: col.setBackground(QColor( 42, 130, 218)) # Highlight interpolated value background else: col.setBackground(QStandardItem("Empty").background()) # Update helper dictionary row.append(col) # Get 2nd Column col = self.items[name]["row"][1] if selected_choice: col.setText(_(selected_choice)) elif type == "string": # Use string value col.setText(memo) elif type == "bool": # Use boolean value if value: col.setText(_("True")) else: col.setText(_("False")) elif type == "color": # Don't output a value for colors col.setText("") elif type == "int": col.setText("%d" % value) else: # Use numeric value col.setText(QLocale().system().toString(float(value), "f", precision=2)) if points > 1: # Apply icon to cell my_icon = QPixmap( os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % interpolation)) col.setData(my_icon, Qt.DecorationRole) # Set the background color of the cell if keyframe: col.setBackground(QColor( "green")) # Highlight keyframe background else: col.setBackground( QColor(42, 130, 218) ) # Highlight interpolated value background else: # clear background color col.setBackground(QStandardItem("Empty").background()) # clear icon my_icon = QPixmap() col.setData(my_icon, Qt.DecorationRole) if type == "color": # Update the color based on the color curves red = property[1]["red"]["value"] green = property[1]["green"]["value"] blue = property[1]["blue"]["value"] col.setBackground(QColor(red, green, blue)) # Update helper dictionary row.append(col) # Keep track of items in a dictionary (for quick look up) self.items[name] = {"row": row, "property": property} # Update the values on the next call to this method (instead of adding rows) self.new_item = False else: # Clear previous properties hash self.previous_hash = "" # Clear previous model data (if any) self.model.clear() # Add Headers self.model.setHorizontalHeaderLabels([_("Property"), _("Value")]) # Done updating model self.ignore_update_signal = False
def remove_keyframe(self, item): """Remove an existing keyframe (if any)""" # Determine what was changed property = self.model.item(item.row(), 0).data() property_name = property[1]["name"] property_type = property[1]["type"] closest_point_x = property[1]["closest_point_x"] property_type = property[1]["type"] property_key = property[0] clip_id, item_type = item.data() # Find this clip c = None clip_updated = False if item_type == "clip": # Get clip object c = Clip.get(id=clip_id) elif item_type == "transition": # Get transition object c = Transition.get(id=clip_id) elif item_type == "effect": # Get effect object c = Effect.get(id=clip_id) if c: # Update clip attribute if property_key in c.data: log.info("remove keyframe: %s" % c.data) # Determine type of keyframe (normal or color) keyframe_list = [] if property_type == "color": keyframe_list = [ c.data[property_key]["red"], c.data[property_key]["blue"], c.data[property_key]["green"] ] else: keyframe_list = [c.data[property_key]] # Loop through each keyframe (red, blue, and green) for keyframe in keyframe_list: # Keyframe # Loop through points, find a matching points on this frame closest_point = None point_to_delete = None for point in keyframe["Points"]: if point["co"]["X"] == self.frame_number: # Found point, Update value clip_updated = True point_to_delete = point break if point["co"]["X"] == closest_point_x: closest_point = point # If no point found, use closest point x if not point_to_delete: point_to_delete = closest_point # Delete point (if needed) if point_to_delete: clip_updated = True log.info("Found point to delete at X=%s" % point_to_delete["co"]["X"]) keyframe["Points"].remove(point_to_delete) # Reduce # of clip properties we are saving (performance boost) c.data = {property_key: c.data[property_key]} # Save changes if clip_updated: # Save c.save() # Update the preview get_app().window.refreshFrameSignal.emit() # Clear selection self.parent.clearSelection()
def color_update(self, item, new_color, interpolation=-1, interpolation_details=[]): """Insert/Update a color keyframe for the selected row""" # Determine what was changed property = self.model.item(item.row(), 0).data() property_type = property[1]["type"] closest_point_x = property[1]["closest_point_x"] previous_point_x = property[1]["previous_point_x"] property_key = property[0] clip_id, item_type = item.data() if property_type == "color": # Find this clip c = None clip_updated = False if item_type == "clip": # Get clip object c = Clip.get(id=clip_id) elif item_type == "transition": # Get transition object c = Transition.get(id=clip_id) elif item_type == "effect": # Get effect object c = Effect.get(id=clip_id) if c: # Update clip attribute if property_key in c.data: log.info("color update: %s" % c.data) # Loop through each keyframe (red, blue, and green) for color, new_value in [("red", new_color.red()), ("blue", new_color.blue()), ("green", new_color.green())]: # Keyframe # Loop through points, find a matching points on this frame found_point = False for point in c.data[property_key][color]["Points"]: log.info("looping points: co.X = %s" % point["co"]["X"]) if interpolation == -1 and point["co"][ "X"] == self.frame_number: # Found point, Update value found_point = True clip_updated = True # Update point point["co"]["Y"] = new_value log.info( "updating point: co.X = %s to value: %s" % (point["co"]["X"], float(new_value))) break elif interpolation > -1 and point["co"][ "X"] == previous_point_x: # Only update interpolation type (and the LEFT side of the curve) found_point = True clip_updated = True point["interpolation"] = interpolation if interpolation == 0: point["handle_right"] = point.get( "handle_right") or { "Y": 0.0, "X": 0.0 } point["handle_right"][ "X"] = interpolation_details[0] point["handle_right"][ "Y"] = interpolation_details[1] log.info( "updating interpolation mode point: co.X = %s to %s" % (point["co"]["X"], interpolation)) log.info("use interpolation preset: %s" % str(interpolation_details)) elif interpolation > -1 and point["co"][ "X"] == closest_point_x: # Only update interpolation type (and the RIGHT side of the curve) found_point = True clip_updated = True point["interpolation"] = interpolation if interpolation == 0: point["handle_left"] = point.get( "handle_left") or { "Y": 0.0, "X": 0.0 } point["handle_left"][ "X"] = interpolation_details[2] point["handle_left"][ "Y"] = interpolation_details[3] log.info( "updating interpolation mode point: co.X = %s to %s" % (point["co"]["X"], interpolation)) log.info("use interpolation preset: %s" % str(interpolation_details)) # Create new point (if needed) if not found_point: clip_updated = True log.info("Created new point at X=%s" % self.frame_number) c.data[property_key][color]["Points"].append({ 'co': { 'X': self.frame_number, 'Y': new_value }, 'interpolation': 1 }) # Reduce # of clip properties we are saving (performance boost) c.data = {property_key: c.data[property_key]} # Save changes if clip_updated: # Save c.save() # Update the preview get_app().window.refreshFrameSignal.emit() # Clear selection self.parent.clearSelection()
def add_file(self, filepath): path, filename = os.path.split(filepath) # Add file into project app = get_app() _ = get_app()._tr # Check for this path in our existing project data file = File.get(path=filepath) # If this file is already found, exit if file: return # Load filepath in libopenshot clip object (which will try multiple readers to open it) clip = openshot.Clip(filepath) # Get the JSON for the clip's internal reader try: reader = clip.Reader() file_data = json.loads(reader.Json()) # Determine media type if file_data["has_video"] and not self.is_image(file_data): file_data["media_type"] = "video" elif file_data["has_video"] and self.is_image(file_data): file_data["media_type"] = "image" elif file_data["has_audio"] and not file_data["has_video"]: file_data["media_type"] = "audio" # Save new file to the project data file = File() file.data = file_data # Is this file an image sequence / animation? image_seq_details = self.get_image_sequence_details(filepath) if image_seq_details: # Update file with correct path folder_path = image_seq_details["folder_path"] file_name = image_seq_details["file_path"] base_name = image_seq_details["base_name"] fixlen = image_seq_details["fixlen"] digits = image_seq_details["digits"] extension = image_seq_details["extension"] if not fixlen: zero_pattern = "%d" else: zero_pattern = "%%0%sd" % digits # Generate the regex pattern for this image sequence pattern = "%s%s.%s" % (base_name, zero_pattern, extension) # Split folder name (parentPath, folderName) = os.path.split(folder_path) if not base_name: # Give alternate name file.data["name"] = "%s (%s)" % (folderName, pattern) # Load image sequence (to determine duration and video_length) image_seq = openshot.Clip(os.path.join(folder_path, pattern)) # Update file details file.data["path"] = os.path.join(folder_path, pattern) file.data["media_type"] = "video" file.data["duration"] = image_seq.Reader().info.duration file.data["video_length"] = image_seq.Reader( ).info.video_length # Save file file.save() return True except: # Handle exception msg = QMessageBox() msg.setText( _("{} is not a valid video, audio, or image file.".format( filename))) msg.exec_() return False
def get_image_sequence_details(self, file_path): """Inspect a file path and determine if this is an image sequence""" # Get just the file name (dirName, fileName) = os.path.split(file_path) extensions = ["png", "jpg", "jpeg", "gif", "tif"] match = re.findall(r"(.*[^\d])?(0*)(\d+)\.(%s)" % "|".join(extensions), fileName, re.I) if not match: # File name does not match an image sequence return None else: # Get the parts of image name base_name = match[0][0] fixlen = match[0][1] > "" number = int(match[0][2]) digits = len(match[0][1] + match[0][2]) extension = match[0][3] full_base_name = os.path.join(dirName, base_name) # Check for images which the file names have the different length fixlen = fixlen or not ( glob.glob("%s%s.%s" % (full_base_name, "[0-9]" * (digits + 1), extension)) or glob.glob("%s%s.%s" % (full_base_name, "[0-9]" * ((digits - 1) if digits > 1 else 3), extension))) # Check for previous or next image for x in range(max(0, number - 100), min(number + 101, 50000)): if x != number and os.path.exists( "%s%s.%s" % (full_base_name, str(x).rjust(digits, "0") if fixlen else str(x), extension)): is_sequence = True break else: is_sequence = False if is_sequence and dirName not in self.ignore_image_sequence_paths: log.info('Prompt user to import image sequence') # Ignore this path (temporarily) self.ignore_image_sequence_paths.append(dirName) # Translate object _ = get_app()._tr # Handle exception ret = QMessageBox.question( self, _("Import Image Sequence"), _("Would you like to import %s as an image sequence?") % fileName, QMessageBox.No | QMessageBox.Yes) if ret == QMessageBox.Yes: # Yes, import image sequence parameters = { "file_path": file_path, "folder_path": dirName, "base_name": base_name, "fixlen": fixlen, "digits": digits, "extension": extension } return parameters else: return None else: return None
def import_xml(): """Import final cut pro XML file""" app = get_app() _ = app._tr # Get FPS info fps_num = app.project.get("fps").get("num", 24) fps_den = app.project.get("fps").get("den", 1) fps_float = float(fps_num / fps_den) # Get XML path recommended_path = app.project.current_filepath or "" if not recommended_path: recommended_path = info.HOME_PATH else: recommended_path = os.path.dirname(recommended_path) file_path = QFileDialog.getOpenFileName(app.window, _("Import XML..."), recommended_path, _("Final Cut Pro (*.xml)"), _("Final Cut Pro (*.xml)"))[0] if not file_path or not os.path.exists(file_path): # User canceled dialog return # Parse XML file xmldoc = minidom.parse(file_path) # Get video tracks video_tracks = [] for video_element in xmldoc.getElementsByTagName("video"): for video_track in video_element.getElementsByTagName("track"): video_tracks.append(video_track) audio_tracks = [] for audio_element in xmldoc.getElementsByTagName("audio"): for audio_track in audio_element.getElementsByTagName("track"): audio_tracks.append(audio_track) # Loop through tracks track_index = 0 for tracks in [audio_tracks, video_tracks]: for track_element in tracks: # Get clipitems on this track (if any) clips_on_track = track_element.getElementsByTagName("clipitem") if not clips_on_track: continue # Get # of tracks track_index += 1 all_tracks = app.project.get("layers") track_number = list( reversed(sorted( all_tracks, key=itemgetter('number'))))[0].get("number") + 1000000 # Create new track above existing layer(s) track = Track() is_locked = False if track_element.getElementsByTagName( "locked")[0].childNodes[0].nodeValue == "TRUE": is_locked = True track.data = { "number": track_number, "y": 0, "label": "XML Import %s" % track_index, "lock": is_locked } track.save() # Loop through clips for clip_element in clips_on_track: # Get clip path xml_file_id = clip_element.getElementsByTagName( "file")[0].getAttribute("id") clip_path = "" if clip_element.getElementsByTagName("pathurl"): clip_path = clip_element.getElementsByTagName( "pathurl")[0].childNodes[0].nodeValue else: # Skip clipitem if no clippath node found # This usually happens for linked audio clips (which OpenShot combines audio and thus ignores this) continue clip_path, is_modified, is_skipped = find_missing_file( clip_path) if is_skipped: continue # Check for this path in our existing project data file = File.get(path=clip_path) # Load filepath in libopenshot clip object (which will try multiple readers to open it) clip_obj = openshot.Clip(clip_path) if not file: # Get the JSON for the clip's internal reader try: reader = clip_obj.Reader() file_data = json.loads(reader.Json()) # Determine media type if file_data["has_video"] and not is_image(file_data): file_data["media_type"] = "video" elif file_data["has_video"] and is_image(file_data): file_data["media_type"] = "image" elif file_data[ "has_audio"] and not file_data["has_video"]: file_data["media_type"] = "audio" # Save new file to the project data file = File() file.data = file_data # Save file file.save() except Exception: # Ignore errors for now pass if (file.data["media_type"] == "video" or file.data["media_type"] == "image"): # Determine thumb path thumb_path = os.path.join(info.THUMBNAIL_PATH, "%s.png" % file.data["id"]) else: # Audio file thumb_path = os.path.join(info.PATH, "images", "AudioThumbnail.png") # Create Clip object clip = Clip() clip.data = json.loads(clip_obj.Json()) clip.data["file_id"] = file.id clip.data["title"] = clip_element.getElementsByTagName( "name")[0].childNodes[0].nodeValue clip.data["layer"] = track.data.get("number", 1000000) clip.data["image"] = thumb_path clip.data["position"] = float( clip_element.getElementsByTagName("start") [0].childNodes[0].nodeValue) / fps_float clip.data["start"] = float( clip_element.getElementsByTagName("in") [0].childNodes[0].nodeValue) / fps_float clip.data["end"] = float( clip_element.getElementsByTagName("out") [0].childNodes[0].nodeValue) / fps_float # Loop through clip's effects for effect_element in clip_element.getElementsByTagName( "effect"): effectid = effect_element.getElementsByTagName( "effectid")[0].childNodes[0].nodeValue keyframes = effect_element.getElementsByTagName("keyframe") if effectid == "opacity": clip.data["alpha"] = {"Points": []} for keyframe_element in keyframes: keyframe_time = float( keyframe_element.getElementsByTagName("when") [0].childNodes[0].nodeValue) keyframe_value = float( keyframe_element.getElementsByTagName("value") [0].childNodes[0].nodeValue) / 100.0 clip.data["alpha"]["Points"].append({ "co": { "X": round(keyframe_time), "Y": keyframe_value }, "interpolation": 1 # linear }) elif effectid == "audiolevels": clip.data["volume"] = {"Points": []} for keyframe_element in keyframes: keyframe_time = float( keyframe_element.getElementsByTagName("when") [0].childNodes[0].nodeValue) keyframe_value = float( keyframe_element.getElementsByTagName("value") [0].childNodes[0].nodeValue) / 100.0 clip.data["volume"]["Points"].append({ "co": { "X": round(keyframe_time), "Y": keyframe_value }, "interpolation": 1 # linear }) # Save clip clip.save() # Update the preview and reselect current frame in properties app.window.refreshFrameSignal.emit() app.window.propertyTableView.select_frame( app.window.preview_thread.player.Position())
def __init__(self, *args): # Invoke parent init QTreeView.__init__(self, *args) # Get a reference to the window object self.app = get_app() self.win = args[0] # Get Model data self.blender_model = BlenderModel() # Keep track of mouse press start position to determine when to start drag self.selected = None self.deselected = None # Preview render timer self.preview_timer = QTimer(self) self.preview_timer.setInterval(300) self.preview_timer.timeout.connect(self.preview_timer_onTimeout) # Init dictionary which holds the values to the template parameters self.params = {} # Assign a new unique id for each template selected self.unique_folder_name = None # Disable interface self.disable_interface(cursor=False) self.selected_template = "" # Setup header columns self.setModel(self.blender_model.model) self.setIconSize(QSize(131, 108)) self.setGridSize(QSize(102, 92)) self.setViewMode(QListView.IconMode) self.setResizeMode(QListView.Adjust) self.setUniformItemSizes(False) self.setWordWrap(True) self.setTextElideMode(Qt.ElideRight) self.setStyleSheet('QTreeView::item { padding-top: 2px; }') # Hook up button self.win.btnRefresh.clicked.connect( functools.partial(self.btnRefresh_clicked)) self.win.sliderPreview.valueChanged.connect( functools.partial(self.sliderPreview_valueChanged)) # Refresh view self.refresh_view() # Background Worker Thread (for Blender process) self.background = QThread(self) self.worker = Worker() # no parent! # Hook up signals to Background Worker self.worker.closed.connect(self.onCloseWindow) self.worker.finished.connect(self.onRenderFinish) self.worker.blender_version_error.connect(self.onBlenderVersionError) self.worker.blender_error_nodata.connect(self.onBlenderErrorNoData) self.worker.progress.connect(self.onUpdateProgress) self.worker.image_updated.connect(self.onUpdateImage) self.worker.blender_error_with_data.connect(self.onBlenderErrorMessage) self.worker.enable_interface.connect(self.onRenableInterface) # Move Worker to new thread, and Start self.worker.moveToThread(self.background) self.background.start()
def __init__(self, *args): # Invoke parent init QListView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.files_model = FilesModel() self.setAcceptDrops(True) self.setDragEnabled(True) self.setDropIndicatorShown(True) self.selected = [] self.ignore_image_sequence_paths = [] # Setup header columns self.setModel(self.files_model.model) self.setIconSize(QSize(131, 108)) self.setViewMode(QListView.IconMode) self.setResizeMode(QListView.Adjust) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setUniformItemSizes(True) self.setWordWrap(True) self.setStyleSheet('QListView::item { padding-top: 2px; }') # Refresh view self.refresh_view() # setup filter events app = get_app() app.window.filesFilter.textChanged.connect(self.filter_changed) app.window.actionFilesClear.triggered.connect(self.clear_filter)
def __init__(self, *args): # Invoke parent init QTreeView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.files_model = FilesModel() # Keep track of mouse press start position to determine when to start drag self.setAcceptDrops(True) self.setDragEnabled(True) self.setDropIndicatorShown(True) self.selected = [] # Setup header columns self.setModel(self.files_model.model) self.setIconSize(QSize(75, 62)) self.setIndentation(0) self.setSelectionBehavior(QTreeView.SelectRows) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) self.setStyleSheet('QTreeView::item { padding-top: 2px; }') # Refresh view self.refresh_view() # setup filter events app = get_app() app.window.filesFilter.textChanged.connect(self.filter_changed) app.window.actionFilesClear.triggered.connect(self.clear_filter) self.files_model.model.itemChanged.connect(self.value_updated)
def __init__(self, *args): # Invoke parent init QListView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.effects_model = EffectsModel() # Keep track of mouse press start position to determine when to start drag self.setAcceptDrops(True) self.setDragEnabled(True) self.setDropIndicatorShown(True) # Setup header columns self.setModel(self.effects_model.model) self.setIconSize(QSize(131, 108)) self.setViewMode(QListView.IconMode) self.setResizeMode(QListView.Adjust) self.setUniformItemSizes(True) self.setWordWrap(True) self.setStyleSheet('QListView::item { padding-top: 2px; }') # Refresh view self.refresh_view() # setup filter events app = get_app() app.window.effectsFilter.textChanged.connect(self.filter_changed) app.window.actionEffectsClear.triggered.connect(self.clear_filter)
def __init__(self, commits, commit_url, *args): # Invoke parent init QListView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.changelog_model = ChangelogModel(commits) self.selected = [] # Setup header columns self.setModel(self.changelog_model.model) self.setIndentation(0) self.setSelectionBehavior(QTreeView.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) self.setStyleSheet('QTreeView::item { padding-top: 2px; }') self.commit_url = commit_url # Refresh view self.refresh_view() # setup filter events app = get_app()