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) log.info(value)
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 updateFrameRate(self): """Callback for changing the frame rate""" log.info('Adjust the framerate of the project') # Adjust the main timeline reader get_app().updates.update(["width"], self.txtWidth.value()) get_app().updates.update(["height"], self.txtHeight.value()) get_app().updates.update(["fps"], {"num" : self.txtFrameRateNum.value(), "den" : self.txtFrameRateDen.value()}) get_app().updates.update(["sample_rate"], self.txtSampleRate.value()) get_app().updates.update(["channels"], self.txtChannels.value()) get_app().updates.update(["channel_layout"], self.cboChannelLayout.currentData()) # Force ApplyMapperToClips to apply these changes get_app().window.timeline_sync.timeline.ApplyMapperToClips() # Determine max frame (based on clips) timeline_length = 0.0 fps = get_app().window.timeline_sync.timeline.info.fps.ToFloat() clips = get_app().window.timeline_sync.timeline.Clips() for clip in clips: clip_last_frame = clip.Position() + clip.Duration() if clip_last_frame > timeline_length: # Set max length of timeline timeline_length = clip_last_frame # Convert to int and round self.timeline_length_int = round(timeline_length * fps) + 1 # Set the min and max frame numbers for this project self.txtStartFrame.setMaximum(self.timeline_length_int) self.txtEndFrame.setMaximum(self.timeline_length_int) self.txtStartFrame.setValue(1) self.txtEndFrame.setValue(self.timeline_length_int)
def Start(self): """ This method starts the video player """ log.info("QThread Start Method Invoked") # Flag to run thread self.is_running = True self.number = None self.videoPreview = self.parent.videoPreview self.player = None self.current_frame = None self.current_mode = None # Init new player self.initPlayer() # Connect player to timeline reader self.player.Reader(self.timeline) self.player.Play() self.player.Pause() # Main loop, waiting for frames to process while self.is_running: # Emit position changed signal (if needed) if self.current_frame != self.player.Position(): self.current_frame = self.player.Position() self.position_changed.emit(self.current_frame) # Emit mode changed signal (if needed) if self.player.Mode() != self.current_mode: self.current_mode = self.player.Mode() self.mode_changed.emit(self.current_mode) # wait for a small delay time.sleep(0.01)
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 actionThumbnailView_trigger(self, event): log.info("Switch to Thumbnail View") # Get settings app = get_app() s = settings.get_settings() # Files if app.context_menu_object == "files": s.set("file_view", "thumbnail") self.tabFiles.layout().removeWidget(self.filesTreeView) self.filesTreeView.deleteLater() self.filesTreeView = None self.filesTreeView = FilesListView(self) self.tabFiles.layout().addWidget(self.filesTreeView) # Transitions elif app.context_menu_object == "transitions": s.set("transitions_view", "thumbnail") self.tabTransitions.layout().removeWidget(self.transitionsTreeView) self.transitionsTreeView.deleteLater() self.transitionsTreeView = None self.transitionsTreeView = TransitionsListView(self) self.tabTransitions.layout().addWidget(self.transitionsTreeView) # Effects elif app.context_menu_object == "effects": s.set("effects_view", "thumbnail") self.tabEffects.layout().removeWidget(self.effectsTreeView) self.effectsTreeView.deleteLater() self.effectsTreeView = None self.effectsTreeView = EffectsListView(self) self.tabEffects.layout().addWidget(self.effectsTreeView)
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 actionDonate_trigger(self, event): try: webbrowser.open("http://openshot.org/donate/") log.info("Open the Donate web page with success") except: QMessageBox.information(self, "Error !", "Unable to open the Donate web page") log.info("Unable to open the Donate web page")
def actionNextMarker_trigger(self, event): log.info("actionNextMarker_trigger") log.info(self.preview_thread.current_frame) # Calculate current position (in seconds) fps = get_app().project.get(["fps"]) fps_float = float(fps["num"]) / float(fps["den"]) current_position = self.preview_thread.current_frame / fps_float # Loop through all markers, and find the closest one to the right closest_position = None for marker in Marker.filter(): marker_position = marker.data["position"] # Is marker smaller than position? if marker_position > current_position: # Is marker larger than previous marker if closest_position and marker_position < closest_position: # Set a new closest marker closest_position = marker_position elif not closest_position: # First one found closest_position = marker_position # Seek to marker position (if any) if closest_position: # Seek frame_to_seek = int(closest_position * fps_float) self.preview_thread.player.Seek(frame_to_seek)
def actionAskQuestion_trigger(self, event): try: webbrowser.open("https://answers.launchpad.net/openshot/+addquestion") log.info("Open the Question launchpad web page with success") except: QMessageBox.information(self, "Error !", "Unable to open the Question web page") log.info("Unable to open the Question web page")
def changed(self, action): # Handle change if action.key and action.key[0] in ["clips", "effects"] and action.type in ["update", "insert"]: log.info(action.values) # Update the model data self.update_model(get_app().window.txtPropertyFilter.text())
def actionTranslate_trigger(self, event): try: webbrowser.open("https://translations.launchpad.net/openshot") log.info("Open the Translate launchpad web page with success") except: QMessageBox.information(self, "Error !", "Unable to open the Translation web page") log.info("Unable to open the Translation web page")
def open_project(self, file_path): """ Open a project from a file path, and refresh the screen """ app = get_app() try: if os.path.exists(file_path.encode('UTF-8')): # Load project file app.project.load(file_path) # Set Window title self.SetWindowTitle() # Reset undo/redo history app.updates.reset() # Refresh file tree self.filesTreeView.refresh_view() # Load recent projects again self.load_recent_menu() log.info("Loaded project {}".format(file_path)) except Exception as ex: log.error("Couldn't save project {}".format(file_path))
def actionReportBug_trigger(self, event): try: webbrowser.open("https://bugs.launchpad.net/openshot/+filebug") log.info("Open the Report Bug Launchpad web page with success") except: QMessageBox.information(self, "Error !", "Unable to open the launchpad web page") log.info("Unable to open the Report Bug launchpad web page")
def move_temp_paths_to_project_folder(self, file_path): """ Move all temp files (such as Blender anmiations) to the project folder. """ # Get project folder new_project_folder = os.path.dirname(file_path) # Get list of files files = self._data["files"] # Loop through each file for file in files: path = file["path"] # Find any temp file paths if info.BLENDER_PATH in path: log.info("TEMP FOLDER DETECTED") # Get folder of file folder_path, file_name = os.path.split(path) parent_path, folder_name = os.path.split(folder_path) # Update path to new folder path = os.path.join(new_project_folder, folder_name) # Copy temp folder to project folder shutil.copytree(folder_path, path) # Update paths in project to new location file["path"] = os.path.join(path, file_name)
def changed(self, action): """ This method is invoked by the UpdateManager each time a change happens (i.e UpdateInterface) """ # Ignore changes that don't affect libopenshot if len(action.key) >= 1 and action.key[0].lower() in ["files", "history", "markers", "layers", "export_path", "import_path", "scale"]: return elif len(action.key) >= 1 and action.key[0].lower() in ["profile"]: # The timeline's profile changed, so update all clips self.timeline.ApplyMapperToClips() return # Pass the change to the libopenshot timeline try: if action.type == "load": # This JSON is initially loaded to libopenshot to update the timeline self.timeline.SetJson(action.json(only_value=True)) self.timeline.Open() # Re-Open the Timeline reader # The timeline's profile changed, so update all clips self.timeline.ApplyMapperToClips() # Refresh current frame (since the entire timeline was updated) self.window.refreshFrameSignal.emit() else: # This JSON DIFF is passed to libopenshot to update the timeline self.timeline.ApplyJsonDiff(action.json(is_array=True)) except Exception as e: log.info("Error applying JSON to timeline object in libopenshot: %s. %s" % (e, action.json(is_array=True)))
def init_slider_values(self): """ Init the slider and preview frame label to the currently selected animation """ log.info("init_slider_values") # Get current preview slider frame preview_frame_number = self.win.sliderPreview.value() length = int(self.params.get("end_frame", 1)) # Get the animation speed (if any) if not self.params.get("animation_speed"): self.params["animation_speed"] = 1 else: # Adjust length (based on animation speed multiplier) length *= int(self.params["animation_speed"]) # Update the preview slider middle_frame = int(length / 2) self.win.sliderPreview.setMinimum(self.params.get("start_frame", 1)) self.win.sliderPreview.setMaximum(length) self.win.sliderPreview.setValue(middle_frame) # Update preview label self.win.lblFrame.setText("{}/{}".format(middle_frame, length)) # Click the refresh button self.btnRefresh_clicked(None)
def save(self, file_path, move_temp_files=True, make_paths_relative=True): """ Save project file to disk """ import openshot log.info("Saving project file: {}".format(file_path)) # Move all temp files (i.e. Blender animations) to the project folder if move_temp_files: self.move_temp_paths_to_project_folder(file_path) # Append version info v = openshot.GetVersion() self._data["version"] = { "openshot-qt" : info.VERSION, "libopenshot" : v.ToString() } # Try to save project settings file, will raise error on failure self.write_to_file(file_path, self._data, path_mode="relative", previous_path=self.current_filepath) # On success, save current filepath self.current_filepath = file_path # Add to recent files setting self.add_to_recent_files(file_path) # Track unsaved changes self.has_unsaved_changes = False
def changed(self, action): # Handle change if action.key and action.key[0] == "clips": log.info(action.values) # Update the model data self.update_model(get_app().window.txtPropertyFilter.text())
def color_button_clicked(self, widget, param, index ): # Show color dialog currentColor = QColor(self.params[param["name"]]) newColor = QColorDialog.getColor(currentColor) widget.setStyleSheet("background-color: {}".format(newColor.name())) self.params[param["name"]] = [newColor.redF(), newColor.greenF(), newColor.blueF()] log.info(newColor.name())
def btnAdvanced_clicked(self): _ = self.app._tr # use an external editor to edit the image try: # Get settings s = settings.get_settings() # get the title editor executable path prog = s.get("title_editor") # launch advanced title editor # debug info log.info("Advanced title editor command: {} {} ".format(prog, self.filename)) p = subprocess.Popen([prog, self.filename]) # wait for process to finish (so we can update the preview) p.communicate() # update image preview self.load_svg_template() self.display_svg() except OSError: msg = QMessageBox() msg.setText(_("Please install {} to use this function").format(prog.capitalize())) msg.exec_()
def save_history(self, project, history_length): """Save history to project""" redo_list = [] undo_list = [] # Loop through each updateAction object and serialize # Ignore any load actions or history update actions history_length_int = int(history_length) for action in self.redoHistory[-history_length_int:]: if action.type != "load" and action.key[0] != "history": actionDict = json.loads(action.json(), strict=False) redo_list.append(actionDict) else: log.info("Saving redo history, skipped key: %s" % str(action.key)) for action in self.actionHistory[-history_length_int:]: if action.type != "load" and action.key[0] != "history": actionDict = json.loads(action.json(), strict=False) undo_list.append(actionDict) else: log.info("Saving undo, skipped key: %s" % str(action.key)) # Set history data in project self.ignore_history = True self.update(["history"], { "redo": redo_list, "undo": undo_list}) self.ignore_history = False
def ChangelogMenuTriggered(self, hash=""): log.info("ChangelogMenuTriggered") try: webbrowser.open(self.commit_url % hash) except: pass
def btnMoveDownClicked(self, event): """Callback for move up button click""" log.info("btnMoveDownClicked") # Get selected file files = self.treeFiles.timeline_model.files selected_index = self.treeFiles.selected.row() # Ignore if empty files if not files: return # New index new_index = min(selected_index + 1, len(files) - 1) log.info(new_index) # Remove item and move it files.insert(new_index, files.pop(selected_index)) # Refresh tree self.treeFiles.refresh_view() # Select new position idx = self.treeFiles.timeline_model.model.index(new_index, 0) self.treeFiles.setCurrentIndex(idx)
def changed(self, action): """ This method is invoked by the UpdateManager each time a change happens (i.e UpdateInterface) """ # Ignore changes that don't affect libopenshot if len(action.key) >= 1 and action.key[0].lower() in ["files", "markers", "layers"]: return elif len(action.key) >= 1 and action.key[0].lower() in ["profile"]: # The timeline's profile changed, so update all clips self.timeline.ApplyMapperToClips() return # Pass the change to the libopenshot timeline try: if action.type == "load": # This JSON is initially loaded to libopenshot to update the timeline self.timeline.SetJson(action.json(only_value=True)) self.timeline.Open() # Re-Open the Timeline reader else: # This JSON DIFF is passed to libopenshot to update the timeline print(action.json(is_array=True)) self.timeline.ApplyJsonDiff(action.json(is_array=True)) except: log.info("Error applying JSON to timeline object in libopenshot")
def btnAdvanced_clicked(self): _ = self.app._tr #use an external editor to edit the image try: prog = "inkscape" #check if inkscape is installed if subprocess.call('which ' + prog + ' 2>/dev/null', shell=True) == 0: # launch Inkscape # debug info log.info("Inkscape command: {} {} ".format(prog, self.filename)) p=subprocess.Popen([prog, self.filename]) # wait for process to finish (so we can update the preview) p.communicate() # update image preview self.load_svg_template() self.display_svg() else: msg = QMessageBox() msg.setText(_("There was an error trying to open {}.").format(prog.capitalize())) msg.exec_() except OSError: msg = QMessageBox() msg.setText(_("Please install {} to use this function").format(prog.capitalize())) msg.exec_()
def btnRemoveClicked(self, event): """Callback for move up button click""" log.info("btnRemoveClicked") # Get selected file files = self.treeFiles.timeline_model.files selected_index = self.treeFiles.selected.row() # Ignore if empty files if not files: return # Remove item files.pop(selected_index) # Refresh tree self.treeFiles.refresh_view() # Select next item (if any) new_index = max(len(files) - 1, 0) # Select new position idx = self.treeFiles.timeline_model.model.index(new_index, 0) self.treeFiles.setCurrentIndex(idx) # Update total self.updateTotal()
def json(self, is_array=False, only_value=False): """ Get the JSON string representing this UpdateAction """ # Build the dictionary to be serialized if only_value: data_dict = copy.deepcopy(self.values) else: data_dict = {"type": self.type, "key": self.key, "value": copy.deepcopy(self.values), "partial": self.partial_update, "old_values": copy.deepcopy(self.old_values)} # Always remove 'history' key (if found). This prevents nested "history" # attributes when a project dict is loaded. try: if data_dict.get("value") and "history" in data_dict.get("value"): data_dict.get("value").pop("history", None) if data_dict.get("old_values") and "history" in data_dict.get("old_values"): data_dict.get("old_values").pop("history", None) except Exception: log.info('Warning: failed to clear history attribute from undo/redo data.') if not is_array: # Use a JSON Object as the root object update_action_dict = data_dict else: # Use a JSON Array as the root object update_action_dict = [data_dict] # Serialize as JSON return json.dumps(update_action_dict)
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 load_history(self, project): """Load history from project""" self.redoHistory.clear() self.actionHistory.clear() # Get history from project data history = project.get(["history"]) # Loop through each, and load serialized data into updateAction objects # Ignore any load actions or history update actions for actionDict in history.get("redo", []): action = UpdateAction() action.load_json(json.dumps(actionDict)) if action.type != "load" and action.key[0] != "history": self.redoHistory.append(action) else: log.info("Loading redo history, skipped key: %s" % str(action.key)) for actionDict in history.get("undo", []): action = UpdateAction() action.load_json(json.dumps(actionDict)) if action.type != "load" and action.key[0] != "history": self.actionHistory.append(action) else: log.info("Loading undo history, skipped key: %s" % str(action.key)) # Notify watchers of new status self.update_watchers()
def load_credit(self): """ Load Credits for everybody who has contribuated in several domain for Openshot """ log.info('Credit screen has been opened') windo = Credits() windo.exec_()
def __init__(self, file=None, 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 # 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 reject(self): """ Cancel button clicked """ log.info('reject') # Accept dialog super(AddToTimeline, self).reject()
def accept(self): """ Ok button clicked """ log.info('accept') # Get settings from form start_position = self.txtStartTime.value() track_num = self.cmbTrack.currentData() fade_value = self.cmbFade.currentData() fade_length = self.txtFadeLength.value() transition_path = self.cmbTransition.currentData() transition_length = self.txtTransitionLength.value() image_length = self.txtImageLength.value() zoom_value = self.cmbZoom.currentData() # Init position position = start_position random_transition = False if transition_path == "random": random_transition = True # Get frames per second fps = get_app().project.get(["fps"]) fps_float = float(fps["num"]) / float(fps["den"]) # Loop through each file (in the current order) for file in self.treeFiles.timeline_model.files: # Create a clip clip = Clip() clip.data = {} 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") # Get file name path, filename = os.path.split(file.data["path"]) # Convert path to the correct relative path (based on this folder) file_path = file.absolute_path() # Create clip object for this file c = openshot.Clip(file_path) # Append missing attributes to Clip JSON new_clip = json.loads(c.Json()) new_clip["position"] = position new_clip["layer"] = track_num new_clip["file_id"] = file.id new_clip["title"] = filename new_clip["image"] = thumb_path # Skip any clips that are missing a 'reader' attribute # TODO: Determine why this even happens, as it shouldn't be possible if not new_clip.get("reader"): continue # Skip to next file # Overwrite frame rate (incase the user changed it in the File Properties) file_properties_fps = float(file.data["fps"]["num"]) / float( file.data["fps"]["den"]) file_fps = float(new_clip["reader"]["fps"]["num"]) / float( new_clip["reader"]["fps"]["den"]) fps_diff = file_fps / file_properties_fps new_clip["reader"]["fps"]["num"] = file.data["fps"]["num"] new_clip["reader"]["fps"]["den"] = file.data["fps"]["den"] # Scale duration / length / and end properties new_clip["reader"]["duration"] *= fps_diff new_clip["end"] *= fps_diff new_clip["duration"] *= fps_diff # Check for optional start and end attributes start_time = 0 end_time = new_clip["reader"]["duration"] if 'start' in file.data.keys(): start_time = file.data['start'] new_clip["start"] = start_time if 'end' in file.data.keys(): end_time = file.data['end'] new_clip["end"] = end_time # Adjust clip duration, start, and end new_clip["duration"] = new_clip["reader"]["duration"] if file.data["media_type"] == "image": end_time = image_length new_clip["end"] = end_time # Adjust Fade of Clips (if no transition is chosen) if not transition_path: if fade_value != None: # Overlap this clip with the previous one (if any) position = max(start_position, new_clip["position"] - fade_length) new_clip["position"] = position if fade_value == 'Fade In' or fade_value == 'Fade In & Out': start = openshot.Point((start_time * fps_float) + 1, 0.0, openshot.BEZIER) start_object = json.loads(start.Json()) end = openshot.Point( min((start_time + fade_length) * fps_float, end_time * fps_float), 1.0, openshot.BEZIER) end_object = json.loads(end.Json()) new_clip['alpha']["Points"].append(start_object) new_clip['alpha']["Points"].append(end_object) if fade_value == 'Fade Out' or fade_value == 'Fade In & Out': start = openshot.Point( max((end_time * fps_float) - (fade_length * fps_float), start_time * fps_float), 1.0, openshot.BEZIER) start_object = json.loads(start.Json()) end = openshot.Point(end_time * fps_float, 0.0, openshot.BEZIER) end_object = json.loads(end.Json()) new_clip['alpha']["Points"].append(start_object) new_clip['alpha']["Points"].append(end_object) # Adjust zoom amount if zoom_value != None: # Location animation if zoom_value == "Random": animate_start_x = uniform(-0.5, 0.5) animate_end_x = uniform(-0.15, 0.15) animate_start_y = uniform(-0.5, 0.5) animate_end_y = uniform(-0.15, 0.15) # Scale animation start_scale = uniform(0.5, 1.5) end_scale = uniform(0.85, 1.15) elif zoom_value == "Zoom In": animate_start_x = 0.0 animate_end_x = 0.0 animate_start_y = 0.0 animate_end_y = 0.0 # Scale animation start_scale = 1.0 end_scale = 1.25 elif zoom_value == "Zoom Out": animate_start_x = 0.0 animate_end_x = 0.0 animate_start_y = 0.0 animate_end_y = 0.0 # Scale animation start_scale = 1.25 end_scale = 1.0 # Add keyframes start = openshot.Point((start_time * fps_float) + 1, start_scale, openshot.BEZIER) start_object = json.loads(start.Json()) end = openshot.Point(end_time * fps_float, end_scale, openshot.BEZIER) end_object = json.loads(end.Json()) new_clip["gravity"] = openshot.GRAVITY_CENTER new_clip["scale_x"]["Points"].append(start_object) new_clip["scale_x"]["Points"].append(end_object) new_clip["scale_y"]["Points"].append(start_object) new_clip["scale_y"]["Points"].append(end_object) # Add keyframes start_x = openshot.Point((start_time * fps_float) + 1, animate_start_x, openshot.BEZIER) start_x_object = json.loads(start_x.Json()) end_x = openshot.Point(end_time * fps_float, animate_end_x, openshot.BEZIER) end_x_object = json.loads(end_x.Json()) start_y = openshot.Point((start_time * fps_float) + 1, animate_start_y, openshot.BEZIER) start_y_object = json.loads(start_y.Json()) end_y = openshot.Point(end_time * fps_float, animate_end_y, openshot.BEZIER) end_y_object = json.loads(end_y.Json()) new_clip["gravity"] = openshot.GRAVITY_CENTER new_clip["location_x"]["Points"].append(start_x_object) new_clip["location_x"]["Points"].append(end_x_object) new_clip["location_y"]["Points"].append(start_y_object) new_clip["location_y"]["Points"].append(end_y_object) if transition_path: # Add transition for this clip (if any) # Open up QtImageReader for transition Image if random_transition: random_index = randint(0, len(self.transitions)) transition_path = self.transitions[random_index] # Get reader for transition transition_reader = openshot.QtImageReader(transition_path) brightness = openshot.Keyframe() brightness.AddPoint(1, 1.0, openshot.BEZIER) brightness.AddPoint( min(transition_length, end_time - start_time) * fps_float, -1.0, openshot.BEZIER) contrast = openshot.Keyframe(3.0) # Create transition dictionary transitions_data = { "layer": track_num, "title": "Transition", "type": "Mask", "start": 0, "end": min(transition_length, end_time - start_time), "brightness": json.loads(brightness.Json()), "contrast": json.loads(contrast.Json()), "reader": json.loads(transition_reader.Json()), "replace_image": False } # Overlap this clip with the previous one (if any) position = max(start_position, position - transition_length) transitions_data["position"] = position new_clip["position"] = position # Create transition tran = Transition() tran.data = transitions_data tran.save() # Save Clip clip.data = new_clip clip.save() # Increment position by length of clip position += (end_time - start_time) # Accept dialog super(AddToTimeline, self).accept()
def Choice_Action_Triggered(self, event): log.info("Choice_Action_Triggered") choice_value = self.sender().data() # Update value of dropdown item self.clip_properties_model.value_updated(self.selected_item, value=choice_value)
def Remove_Action_Triggered(self, event): log.info("Remove_Action_Triggered") self.clip_properties_model.remove_keyframe(self.selected_item)
def load_license(self): """ Load License of the project """ log.info('License screen has been opened') windo = License() windo.exec_()
os.path.join(PATH, "openshot_qt", "launch-openshot")) if os.path.exists(os.path.join(PATH, "openshot_qt")): # Append path to system path sys.path.append(os.path.join(PATH, "openshot_qt")) print("Loaded modules from openshot_qt directory: %s" % os.path.join(PATH, "openshot_qt")) # Append possible build server paths sys.path.insert(0, os.path.join(PATH, "build", "install-x86", "lib")) sys.path.insert(0, os.path.join(PATH, "build", "install-x64", "lib")) from classes import info from classes.logger import log log.info("Execution path: %s" % os.path.abspath(__file__)) # Find files matching patterns def find_files(directory, patterns): """ Recursively find all files in a folder tree """ for root, dirs, files in os.walk(directory): for basename in files: if ".pyc" not in basename and "__pycache__" not in basename: for pattern in patterns: if fnmatch.fnmatch(basename, pattern): filename = os.path.join(root, basename) yield filename # GUI applications require a different base on Windows
def close(self): """ Actually close window and accept dialog """ log.info('close')
def update_model(self, clear=True): log.info("updating files model.") app = get_app() # Get window to check filters win = app.window _ = app._tr # Skip updates (if needed) if self.ignore_update_signal: return # Clear all items if clear: self.model_ids = {} self.model.clear() # Add Headers self.model.setHorizontalHeaderLabels( ["", _("Name"), _("Tags"), "", "", ""]) # Get list of files in project files = File.filter() # get all files # add item for each file for file in files: path, filename = os.path.split(file.data["path"]) tags = "" if "tags" in file.data.keys(): tags = file.data["tags"] name = filename if "name" in file.data.keys(): name = file.data["name"] if not win.actionFilesShowAll.isChecked(): if win.actionFilesShowVideo.isChecked(): if not file.data["media_type"] == "video": continue # to next file, didn't match filter elif win.actionFilesShowAudio.isChecked(): if not file.data["media_type"] == "audio": continue # to next file, didn't match filter elif win.actionFilesShowImage.isChecked(): if not file.data["media_type"] == "image": continue # to next file, didn't match filter if win.filesFilter.text() != "": if not win.filesFilter.text().lower() in filename.lower() \ and not win.filesFilter.text().lower() in tags.lower() \ and not win.filesFilter.text().lower() in name.lower(): continue # Generate thumbnail for file (if needed) if (file.data["media_type"] == "video" or file.data["media_type"] == "image"): # Determine thumb path thumb_path = os.path.join(info.THUMBNAIL_PATH, "{}.png".format(file.id)) # Check if thumb exists if not os.path.exists(thumb_path): try: # Convert path to the correct relative path (based on this folder) file_path = file.absolute_path() # Reload this reader clip = openshot.Clip(file_path) reader = clip.Reader() # Open reader reader.Open() # Determine if video overlay should be applied to thumbnail overlay_path = "" if file.data["media_type"] == "video": overlay_path = os.path.join( info.IMAGES_PATH, "overlay.png") # Check for start and end attributes (optional) thumbnail_frame = 1 if 'start' in file.data.keys(): fps = file.data["fps"] fps_float = float(fps["num"]) / float(fps["den"]) thumbnail_frame = round( float(file.data['start']) * fps_float) + 1 # Save thumbnail reader.GetFrame(thumbnail_frame).Thumbnail( thumb_path, 98, 64, os.path.join(info.IMAGES_PATH, "mask.png"), overlay_path, "#000", False) reader.Close() clip.Close() except: # Handle exception msg = QMessageBox() msg.setText( _("{} is not a valid video, audio, or image file.". format(filename))) msg.exec_() continue else: # Audio file thumb_path = os.path.join(info.PATH, "images", "AudioThumbnail.png") row = [] # Append thumbnail col = QStandardItem() col.setIcon(QIcon(thumb_path)) col.setText(name) col.setToolTip(filename) col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) row.append(col) # Append Filename col = QStandardItem("Name") col.setData(filename, Qt.DisplayRole) col.setText(name) col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled | Qt.ItemIsEditable) row.append(col) # Append Tags col = QStandardItem("Tags") col.setData(tags, Qt.DisplayRole) col.setText(tags) col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled | Qt.ItemIsEditable) row.append(col) # Append Media Type col = QStandardItem("Type") col.setData(file.data["media_type"], Qt.DisplayRole) col.setText(file.data["media_type"]) col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled | Qt.ItemIsEditable) row.append(col) # Append Path col = QStandardItem("Path") col.setData(path, Qt.DisplayRole) col.setText(path) col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsDragEnabled) row.append(col) # Append ID col = QStandardItem("ID") col.setData(file.data["id"], Qt.DisplayRole) col.setText(file.data["id"]) col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsDragEnabled) row.append(col) # Append ROW to MODEL (if does not already exist in model) if not file.data["id"] in self.model_ids: self.model.appendRow(row) self.model_ids[file.data["id"]] = file.data["id"] # Process events in QT (to keep the interface responsive) app.processEvents() # Refresh view and filters (to hide or show this new item) get_app().window.resize_contents() # Emit signal self.model.ModelRefreshed.emit()
def btnClear_clicked(self): """Clear the current clip and reset the form""" log.info('btnClear_clicked') # Reset form self.clearForm()
def reject(self): log.info('reject')
def cancel(self): log.info("Exit the dialog of super resolution. ") super(SuperResolution, self).reject()
def accept(self): """ Ok button clicked """ log.info('accept')
def onModeChanged(self, current_mode): log.info('onModeChanged %s', current_mode) self.PlayModeChangedSignal.emit(current_mode)
def init_language(): """ Find the current locale, and install the correct translators """ # Get app instance app = QCoreApplication.instance() # Setup of our list of translators and paths translator_types = ( { "type": 'QT', "pattern": 'qt_%s', # Older versions of Qt use this file (built-in translations) "path": QLibraryInfo.location(QLibraryInfo.TranslationsPath) }, { "type": 'QT', "pattern": 'qtbase_%s', # Newer versions of Qt use this file (built-in translations) "path": QLibraryInfo.location(QLibraryInfo.TranslationsPath) }, { "type": 'QT', "pattern": 'qt_%s', "path": os.path.join(info.PATH, 'locale', 'QT') }, # Optional path where we package QT translations { "type": 'QT', "pattern": 'qtbase_%s', "path": os.path.join(info.PATH, 'locale', 'QT') }, # Optional path where we package QT translations { "type": 'OpenShot', "pattern": os.path.join('%s', 'LC_MESSAGES', 'OpenShot'), # Our custom translations "path": os.path.join(info.PATH, 'locale') }, ) # Determine the environment locale, or default to system locale name locale_names = [ os.environ.get('LANG', QLocale().system().name()), os.environ.get('LOCALE', QLocale().system().name()) ] # Determine if the user has overwritten the language (in the preferences) preference_lang = settings.get_settings().get('default-language') if preference_lang != "Default": # Append preference lang to top of list locale_names.insert(0, preference_lang) # Output all system languages detected log.info("Qt Detected Languages: {}".format( QLocale().system().uiLanguages())) log.info("LANG Environment Variable: {}".format( os.environ.get('LANG', QLocale().system().name()))) log.info("LOCALE Environment Variable: {}".format( os.environ.get('LOCALE', QLocale().system().name()))) # Default the locale to C, for number formatting locale.setlocale(locale.LC_ALL, 'C') # Loop through environment variables found_language = False for locale_name in locale_names: # Don't try on default locale, since it fails to load what is the default language if QLocale().system().name() in locale_name: log.info("Skipping English language (no need for translation): {}". format(locale_name)) continue # Go through each translator and try to add for current locale for type in translator_types: trans = QTranslator(app) if find_language_match(type["pattern"], type["path"], trans, locale_name): # Install translation app.installTranslator(trans) found_language = True # Exit if found language if found_language: log.info( "Exiting translation system (since we successfully loaded: {})" .format(locale_name)) info.CURRENT_LANGUAGE = locale_name break
def onRenableInterface(self): log.info('onRenableInterface') self.enable_interface()
def uploadSequence(self): """ Start exporting video """ # get translations _ = get_app()._tr # Init progress bar # 应该仅仅是用来展示进度条 # self.progressExportVideo.setMinimum(self.txtStartFrame.value()) # self.progressExportVideo.setMaximum(self.txtEndFrame.value()) # self.progressExportVideo.setValue(self.txtStartFrame.value()) # 这个是默认的图片文件输出格式 self.image_format = "-%05d.png" export_type = _("Image Sequence") # Determine final exported file path (and replace blank paths with default ones) default_filename = "IM" default_folder = os.path.join(info.HOME_PATH, 'Desktop/temp') # 如果要导出图片序列,就规定好导出文件的命名 file_name_with_ext = "%s%s" % (default_filename, self.image_format.strip()) # 确定导出文件的路径 export_file_path = os.path.join(default_folder, file_name_with_ext) log.info("锁定了的文件保存路径: %s" % export_file_path) # Init export settings # 以下的设定全部都已经写死了 video_settings = { "vformat": 'mp4', "vcodec": 'libx264', "fps": { "num": 25, "den": 1 }, "width": 1024, "height": 576, "pixel_ratio": { "num": 1, "den": 1 }, "video_bitrate": 15000000, "start_frame": 1, "end_frame": 17 } audio_settings = { "acodec": 'aac', "sample_rate": 48000, "channels": 2, "channel_layout": 3, "audio_bitrate": 192000 } # Override vcodec and format for Image Sequences image_ext = os.path.splitext(self.image_format.strip())[1].replace( ".", "") video_settings["vformat"] = image_ext if image_ext in ["jpg", "jpeg"]: video_settings["vcodec"] = "mjpeg" else: video_settings["vcodec"] = image_ext # Store updated export folder path in project file get_app().updates.update_untracked(["export_path"], os.path.dirname(export_file_path)) # Mark project file as unsaved get_app().project.has_unsaved_changes = True # Set MaxSize (so we don't have any downsampling) self.timeline.SetMaxSize(video_settings.get("width"), video_settings.get("height")) # Set lossless cache settings (temporarily) export_cache_object = openshot.CacheMemory(500) self.timeline.SetCache(export_cache_object) # Rescale all keyframes (if needed) if self.export_fps_factor != 1.0: log.info("导出文件fps因子不为1") # Get a copy of rescaled project data (this does not modify the active project) rescaled_app_data = get_app().project.rescale_keyframes( self.export_fps_factor) # Load the "export" Timeline reader with the JSON from the real timeline self.timeline.SetJson(json.dumps(rescaled_app_data)) # Re-update the timeline FPS again (since the timeline just got clobbered) self.updateFrameRate() # Create FFmpegWriter try: w = openshot.FFmpegWriter(export_file_path) # Set video options if export_type in [ _("Video & Audio"), _("Video Only"), _("Image Sequence") ]: w.SetVideoOptions( True, video_settings.get("vcodec"), openshot.Fraction( video_settings.get("fps").get("num"), video_settings.get("fps").get("den")), video_settings.get("width"), video_settings.get("height"), openshot.Fraction( video_settings.get("pixel_ratio").get("num"), video_settings.get("pixel_ratio").get("den")), False, False, video_settings.get("video_bitrate")) # Prepare the streams w.PrepareStreams() # These extra options should be set in an extra method # No feedback is given to the user # TODO: Tell user if option is not available # Muxing options for mp4/mov w.SetOption(openshot.VIDEO_STREAM, "muxing_preset", "mp4_faststart") # Set the quality in case crf was selected # if "crf" in self.txtVideoBitRate.text(): # w.SetOption(openshot.VIDEO_STREAM, "crf", str(int(video_settings.get("video_bitrate")))) # # Set the quality in case qp was selected # if "qp" in self.txtVideoBitRate.text(): # w.SetOption(openshot.VIDEO_STREAM, "qp", str(int(video_settings.get("video_bitrate")))) # Open the writer w.Open() # Notify window of export started title_message = "" get_app().window.ExportStarted.emit( export_file_path, video_settings.get("start_frame"), video_settings.get("end_frame")) progressstep = max( 1, round((video_settings.get("end_frame") - video_settings.get("start_frame")) / 1000)) start_time_export = time.time() start_frame_export = video_settings.get("start_frame") end_frame_export = video_settings.get("end_frame") # Write each frame in the selected range # 接下来就是导出动作的重要内容 for frame in range(video_settings.get("start_frame"), video_settings.get("end_frame") + 1): # Update progress bar (emit signal to main window) if (frame % progressstep) == 0: end_time_export = time.time() if (((frame - start_frame_export) != 0) & ((end_time_export - start_time_export) != 0)): seconds_left = round( (start_time_export - end_time_export) * (frame - end_frame_export) / (frame - start_frame_export)) fps_encode = ((frame - start_frame_export) / (end_time_export - start_time_export)) title_message = _( "%(hours)d:%(minutes)02d:%(seconds)02d Remaining (%(fps)5.2f FPS)" ) % { 'hours': seconds_left / 3600, 'minutes': (seconds_left / 60) % 60, 'seconds': seconds_left % 60, 'fps': fps_encode } # Emit frame exported # get_app().window.ExportFrame.emit(title_message, video_settings.get("start_frame"), video_settings.get("end_frame"), frame) # Process events (to show the progress bar moving) # QCoreApplication.processEvents() # Write the frame object to the video w.WriteFrame(self.timeline.GetFrame(frame)) # Check if we need to bail out # if not self.exporting: # break # Close writer w.Close() # 下面的内容应该都是配合进度提示的,删除 ''' # Emit final exported frame (with elapsed time) seconds_run = round((end_time_export - start_time_export)) title_message = _("%(hours)d:%(minutes)02d:%(seconds)02d Elapsed (%(fps)5.2f FPS)") % { 'hours': seconds_run / 3600, 'minutes': (seconds_run / 60) % 60, 'seconds': seconds_run % 60, 'fps': fps_encode} get_app().window.ExportFrame.emit(title_message, video_settings.get("start_frame"), video_settings.get("end_frame"), frame) ''' except Exception as e: # TODO: Find a better way to catch the error. This is the only way I have found that # does not throw an error error_type_str = str(e) log.info("Error type string: %s" % error_type_str) if "InvalidChannels" in error_type_str: log.info("Error setting invalid # of channels (%s)" % (audio_settings.get("channels"))) track_metric_error("invalid-channels-%s-%s-%s-%s" % (video_settings.get("vformat"), video_settings.get("vcodec"), audio_settings.get("acodec"), audio_settings.get("channels"))) elif "InvalidSampleRate" in error_type_str: log.info("Error setting invalid sample rate (%s)" % (audio_settings.get("sample_rate"))) track_metric_error("invalid-sample-rate-%s-%s-%s-%s" % (video_settings.get("vformat"), video_settings.get("vcodec"), audio_settings.get("acodec"), audio_settings.get("sample_rate"))) elif "InvalidFormat" in error_type_str: log.info("Error setting invalid format (%s)" % (video_settings.get("vformat"))) track_metric_error("invalid-format-%s" % (video_settings.get("vformat"))) elif "InvalidCodec" in error_type_str: log.info("Error setting invalid codec (%s/%s/%s)" % (video_settings.get("vformat"), video_settings.get("vcodec"), audio_settings.get("acodec"))) track_metric_error("invalid-codec-%s-%s-%s" % (video_settings.get("vformat"), video_settings.get("vcodec"), audio_settings.get("acodec"))) elif "ErrorEncodingVideo" in error_type_str: log.info("Error encoding video frame (%s/%s/%s)" % (video_settings.get("vformat"), video_settings.get("vcodec"), audio_settings.get("acodec"))) track_metric_error("video-encode-%s-%s-%s" % (video_settings.get("vformat"), video_settings.get("vcodec"), audio_settings.get("acodec"))) # Show friendly error friendly_error = error_type_str.split("> ")[0].replace("<", "") # Prompt error message msg = QMessageBox() msg.setWindowTitle(_("Export Error")) msg.setText( _("Sorry, there was an error exporting your video: \n%s") % friendly_error) msg.exec_() # Notify window of export started get_app().window.ExportEnded.emit(export_file_path) # Close timeline object self.timeline.Close() # Clear all cache self.timeline.ClearAllCache() # Re-set OMP thread enabled flag if self.s.get("omp_threads_enabled"): openshot.Settings.Instance().WAIT_FOR_VIDEO_PROCESSING_TASK = False else: openshot.Settings.Instance().WAIT_FOR_VIDEO_PROCESSING_TASK = True # Return scale mode to lower quality scaling (for faster previews) openshot.Settings.Instance().HIGH_QUALITY_SCALING = False # Handle end of export (for non-canceled exports) # if self.s.get("show_finished_window") and self.exporting: # # Hide cancel and export buttons # self.cancel_button.setVisible(False) # self.export_button.setVisible(False) # # # Reveal done button # self.close_button.setVisible(True) # # # Make progress bar green (to indicate we are done) # # from PyQt5.QtGui import QPalette # # p = QPalette() # # p.setColor(QPalette.Highlight, Qt.green) # # self.progressExportVideo.setPalette(p) # # # Raise the window # self.show() # else: # # Accept dialog # super(SuperResolution, self).accept() success_hint = QDialog() success_hint.setWindowTitle("成功") success_hint.exec_()
def onBlenderErrorNoData(self): log.info('onBlenderErrorNoData') self.error_with_blender()
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.62: # 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)) # 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 onRenderFinish(self): log.info('onRenderFinish') self.render_finished()
def onBlenderErrorMessage(self, error): log.info('onBlenderErrorMessage') self.error_with_blender(None, error)
def currentChanged(self, selected, deselected): # Get selected item self.selected = selected self.deselected = deselected # Get translation object _ = self.app._tr # Clear existing settings self.win.clear_effect_controls() # Get animation details animation = self.get_animation_details() self.selected_template = animation["service"] # Assign a new unique id for each template selected self.generateUniqueFolder() # Loop through params for param in animation["params"]: log.info(param["title"]) # Is Hidden Param? if param["name"] == "start_frame" or param["name"] == "end_frame": # add value to dictionary self.params[param["name"]] = int(param["default"]) # skip to next param without rendering the controls continue # Create Label widget = None label = QLabel() label.setText(_(param["title"])) label.setToolTip(_(param["title"])) if param["type"] == "spinner": # add value to dictionary self.params[param["name"]] = float(param["default"]) # create spinner widget = QDoubleSpinBox() widget.setMinimum(float(param["min"])) widget.setMaximum(float(param["max"])) widget.setValue(float(param["default"])) widget.setSingleStep(0.01) widget.setToolTip(param["title"]) widget.valueChanged.connect( functools.partial(self.spinner_value_changed, param)) elif param["type"] == "text": # add value to dictionary self.params[param["name"]] = _(param["default"]) # create spinner widget = QLineEdit() widget.setText(_(param["default"])) widget.textChanged.connect( functools.partial(self.text_value_changed, widget, param)) elif param["type"] == "multiline": # add value to dictionary self.params[param["name"]] = _(param["default"]) # create spinner widget = QTextEdit() widget.setText(_(param["default"]).replace("\\n", "\n")) widget.textChanged.connect( functools.partial(self.text_value_changed, widget, param)) elif param["type"] == "dropdown": # add value to dictionary self.params[param["name"]] = param["default"] # create spinner widget = QComboBox() widget.currentIndexChanged.connect( functools.partial(self.dropdown_index_changed, widget, param)) # Add values to dropdown if "project_files" in param["name"]: # override files dropdown param["values"] = {} for file in File.filter(): if file.data["media_type"] in ("image", "video"): (dirName, fileName) = os.path.split(file.data["path"]) (fileBaseName, fileExtension) = os.path.splitext(fileName) if fileExtension.lower() not in (".svg"): param["values"][fileName] = "|".join( (file.data["path"], str(file.data["height"]), str(file.data["width"]), file.data["media_type"], str(file.data["fps"]["num"] / file.data["fps"]["den"]))) # Add normal values box_index = 0 for k, v in sorted(param["values"].items()): # add dropdown item widget.addItem(_(k), v) # select dropdown (if default) if v == param["default"]: widget.setCurrentIndex(box_index) box_index = box_index + 1 if not param["values"]: widget.addItem(_("No Files Found"), "") widget.setEnabled(False) elif param["type"] == "color": # add value to dictionary color = QColor(param["default"]) self.params[param["name"]] = [ color.redF(), color.greenF(), color.blueF() ] widget = QPushButton() widget.setText("") widget.setStyleSheet("background-color: {}".format( param["default"])) widget.clicked.connect( functools.partial(self.color_button_clicked, widget, param)) # Add Label and Widget to the form if (widget and label): self.win.settingsContainer.layout().addRow(label, widget) elif (label): self.win.settingsContainer.layout().addRow(label) # Enable interface self.enable_interface() # Init slider values self.init_slider_values()
def onBlenderVersionError(self, version): log.info('onBlenderVersionError') self.error_with_blender(version)
def dropdown_index_changed(self, widget, param, index): value = widget.itemData(index) self.params[param["name"]] = value log.info(value)
def onCloseWindow(self): log.info('onCloseWindow') self.close()
def log_message(self, msg_format, *args): """ Log message from HTTPServer """ log.info(msg_format % args)
def close_window(self): log.info("CLOSING WINDOW") # Close window self.close()
def kill(self): self.running = False log.info('Shutting down thumbnail server: %s' % str(self.server_address)) self.thumbServer.shutdown()
def spinner_value_changed(self, param, value): self.params[param["name"]] = value log.info(value)