def actionRemoveTrack_trigger(self, event): log.info('actionRemoveTrack_trigger') for track_id in self.selected_tracks: tracks = Track.filter(id=track_id) for t in tracks: # Remove track t.delete()
def __init__(self, files=None, position=0.0): # 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 settings self.settings = settings.get_settings() # Get translation object self.app = get_app() _ = self.app._tr # Track metrics track_metric_screen("add-to-timeline-screen") # Add custom treeview to window self.treeFiles = TimelineTreeView(self) self.vboxTreeParent.insertWidget(0, self.treeFiles) # Update data in model self.treeFiles.timeline_model.update_model(files) # Refresh view self.treeFiles.refresh_view() # Init start position self.txtStartTime.setValue(position) # Init default image length self.txtImageLength.setValue(self.settings.get("default-image-length")) self.txtImageLength.valueChanged.connect(self.updateTotal) self.cmbTransition.currentIndexChanged.connect(self.updateTotal) self.cmbFade.currentIndexChanged.connect(self.updateTotal) self.txtFadeLength.valueChanged.connect(self.updateTotal) self.txtTransitionLength.valueChanged.connect(self.updateTotal) # Add all tracks to dropdown tracks = Track.filter() for track in reversed(tracks): # Add to dropdown self.cmbTrack.addItem(_('Track %s' % track.data['number']), track.data['number']) # Add all fade options self.cmbFade.addItem(_('None'), None) self.cmbFade.addItem(_('Fade In'), 'Fade In') self.cmbFade.addItem(_('Fade Out'), 'Fade Out') self.cmbFade.addItem(_('Fade In & Out'), 'Fade In & Out') # Add all zoom options self.cmbZoom.addItem(_('None'), None) self.cmbZoom.addItem(_('Random'), 'Random') self.cmbZoom.addItem(_('Zoom In'), 'Zoom In') self.cmbZoom.addItem(_('Zoom Out'), 'Zoom Out') # Add all transitions transitions_dir = os.path.join(info.PATH, "transitions") common_dir = os.path.join(transitions_dir, "common") extra_dir = os.path.join(transitions_dir, "extra") transition_groups = [{ "type": "common", "dir": common_dir, "files": os.listdir(common_dir) }, { "type": "extra", "dir": extra_dir, "files": os.listdir(extra_dir) }] self.cmbTransition.addItem(_('None'), None) self.cmbTransition.addItem(_('Random'), 'random') self.transitions = [] for group in transition_groups: type = group["type"] dir = group["dir"] files = group["files"] for filename in sorted(files): path = os.path.join(dir, filename) (fileBaseName, fileExtension) = os.path.splitext(filename) # Skip hidden files (such as .DS_Store, etc...) if filename[0] == "." or "thumbs.db" in filename.lower(): continue # split the name into parts (looking for a number) suffix_number = None name_parts = fileBaseName.split("_") if name_parts[-1].isdigit(): suffix_number = name_parts[-1] # get name of transition trans_name = fileBaseName.replace("_", " ").capitalize() # replace suffix number with placeholder (if any) if suffix_number: trans_name = trans_name.replace(suffix_number, "%s") trans_name = _(trans_name) % suffix_number else: trans_name = _(trans_name) # Check for thumbnail path (in build-in cache) thumb_path = os.path.join(info.IMAGES_PATH, "cache", "{}.png".format(fileBaseName)) # Check built-in cache (if not found) if not os.path.exists(thumb_path): # Check user folder cache thumb_path = os.path.join(info.CACHE_PATH, "{}.png".format(fileBaseName)) # Add item self.transitions.append(path) self.cmbTransition.addItem(QIcon(thumb_path), _(trans_name), path) # Connections self.btnMoveUp.clicked.connect(self.btnMoveUpClicked) self.btnMoveDown.clicked.connect(self.btnMoveDownClicked) self.btnShuffle.clicked.connect(self.btnShuffleClicked) self.btnRemove.clicked.connect(self.btnRemoveClicked) self.btnBox.accepted.connect(self.accept) self.btnBox.rejected.connect(self.reject) # Update total self.updateTotal()
def read_legacy_project_file(self, file_path): """Attempt to read a legacy version 1.x openshot project file""" import sys, pickle from classes.query import File, Track, Clip, Transition from classes.app import get_app import openshot try: import json except ImportError: import simplejson as json # Get translation method _ = get_app()._tr # Append version info v = openshot.GetVersion() project_data = {} project_data["version"] = { "openshot-qt": info.VERSION, "libopenshot": v.ToString() } # Get FPS from project from classes.app import get_app fps = get_app().project.get(["fps"]) fps_float = float(fps["num"]) / float(fps["den"]) # Import legacy openshot classes (from version 1.X) from classes.legacy.openshot import classes as legacy_classes from classes.legacy.openshot.classes import project as legacy_project from classes.legacy.openshot.classes import sequences as legacy_sequences from classes.legacy.openshot.classes import track as legacy_track from classes.legacy.openshot.classes import clip as legacy_clip from classes.legacy.openshot.classes import keyframe as legacy_keyframe from classes.legacy.openshot.classes import files as legacy_files from classes.legacy.openshot.classes import transition as legacy_transition from classes.legacy.openshot.classes import effect as legacy_effect from classes.legacy.openshot.classes import marker as legacy_marker sys.modules['openshot.classes'] = legacy_classes sys.modules['classes.project'] = legacy_project sys.modules['classes.sequences'] = legacy_sequences sys.modules['classes.track'] = legacy_track sys.modules['classes.clip'] = legacy_clip sys.modules['classes.keyframe'] = legacy_keyframe sys.modules['classes.files'] = legacy_files sys.modules['classes.transition'] = legacy_transition sys.modules['classes.effect'] = legacy_effect sys.modules['classes.marker'] = legacy_marker # Keep track of files that failed to load failed_files = [] with open(file_path.encode('UTF-8'), 'rb') as f: try: # Unpickle legacy openshot project file v1_data = pickle.load(f, fix_imports=True, encoding="UTF-8") file_lookup = {} # Loop through files for item in v1_data.project_folder.items: # Is this item a File (i.e. ignore folders) if isinstance(item, legacy_files.OpenShotFile): # Create file try: clip = openshot.Clip(item.name) 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 file.save() # Keep track of new ids and old ids file_lookup[item.unique_id] = file except: # Handle exception quietly msg = ( "%s is not a valid video, audio, or image file." % item.name) log.error(msg) failed_files.append(item.name) # Delete all tracks track_list = copy.deepcopy(Track.filter()) for track in track_list: track.delete() # Create new tracks track_counter = 0 for legacy_t in reversed(v1_data.sequences[0].tracks): t = Track() t.data = { "number": track_counter, "y": 0, "label": legacy_t.name } t.save() track_counter += 1 # Loop through clips track_counter = 0 for sequence in v1_data.sequences: for track in reversed(sequence.tracks): for clip in track.clips: # Get associated file for this clip if clip.file_object.unique_id in file_lookup.keys( ): file = file_lookup[clip.file_object.unique_id] else: # Skip missing file log.info( "Skipping importing missing file: %s" % clip.file_object.unique_id) continue # Create clip 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["file_id"] = file.id new_clip["title"] = filename new_clip["image"] = thumb_path # Check for optional start and end attributes new_clip["start"] = clip.start_time new_clip["end"] = clip.end_time new_clip["position"] = clip.position_on_track new_clip["layer"] = track_counter # Clear alpha (if needed) if clip.video_fade_in or clip.video_fade_out: new_clip["alpha"]["Points"] = [] # Video Fade IN if clip.video_fade_in: # Add keyframes start = openshot.Point( round(clip.start_time * fps_float) + 1, 0.0, openshot.BEZIER) start_object = json.loads(start.Json()) end = openshot.Point( round((clip.start_time + clip.video_fade_in_amount) * fps_float) + 1, 1.0, openshot.BEZIER) end_object = json.loads(end.Json()) new_clip["alpha"]["Points"].append( start_object) new_clip["alpha"]["Points"].append(end_object) # Video Fade OUT if clip.video_fade_out: # Add keyframes start = openshot.Point( round((clip.end_time - clip.video_fade_out_amount) * fps_float) + 1, 1.0, openshot.BEZIER) start_object = json.loads(start.Json()) end = openshot.Point( round(clip.end_time * fps_float) + 1, 0.0, openshot.BEZIER) end_object = json.loads(end.Json()) new_clip["alpha"]["Points"].append( start_object) new_clip["alpha"]["Points"].append(end_object) # Clear Audio (if needed) if clip.audio_fade_in or clip.audio_fade_out: new_clip["volume"]["Points"] = [] else: p = openshot.Point(1, clip.volume / 100.0, openshot.BEZIER) p_object = json.loads(p.Json()) new_clip["volume"] = {"Points": [p_object]} # Audio Fade IN if clip.audio_fade_in: # Add keyframes start = openshot.Point( round(clip.start_time * fps_float) + 1, 0.0, openshot.BEZIER) start_object = json.loads(start.Json()) end = openshot.Point( round((clip.start_time + clip.video_fade_in_amount) * fps_float) + 1, clip.volume / 100.0, openshot.BEZIER) end_object = json.loads(end.Json()) new_clip["volume"]["Points"].append( start_object) new_clip["volume"]["Points"].append(end_object) # Audio Fade OUT if clip.audio_fade_out: # Add keyframes start = openshot.Point( round((clip.end_time - clip.video_fade_out_amount) * fps_float) + 1, clip.volume / 100.0, openshot.BEZIER) start_object = json.loads(start.Json()) end = openshot.Point( round(clip.end_time * fps_float) + 1, 0.0, openshot.BEZIER) end_object = json.loads(end.Json()) new_clip["volume"]["Points"].append( start_object) new_clip["volume"]["Points"].append(end_object) # Save clip clip_object = Clip() clip_object.data = new_clip clip_object.save() # Loop through transitions for trans in track.transitions: # Fix default transition if not trans.resource or not os.path.exists( trans.resource): trans.resource = os.path.join( info.PATH, "transitions", "common", "fade.svg") # Open up QtImageReader for transition Image transition_reader = openshot.QtImageReader( trans.resource) trans_begin_value = 1.0 trans_end_value = -1.0 if trans.reverse: trans_begin_value = -1.0 trans_end_value = 1.0 brightness = openshot.Keyframe() brightness.AddPoint(1, trans_begin_value, openshot.BEZIER) brightness.AddPoint( round(trans.length * fps_float) + 1, trans_end_value, openshot.BEZIER) contrast = openshot.Keyframe(trans.softness * 10.0) # Create transition dictionary transitions_data = { "id": get_app().project.generate_id(), "layer": track_counter, "title": "Transition", "type": "Mask", "position": trans.position_on_track, "start": 0, "end": trans.length, "brightness": json.loads(brightness.Json()), "contrast": json.loads(contrast.Json()), "reader": json.loads(transition_reader.Json()), "replace_image": False } # Save transition t = Transition() t.data = transitions_data t.save() # Increment track counter track_counter += 1 except Exception as ex: # Error parsing legacy contents msg = _("Failed to load project file %(path)s: %(error)s" % { "path": file_path, "error": ex }) log.error(msg) raise Exception(msg) # Show warning if some files failed to load if failed_files: # Throw exception raise Exception( _("Failed to load the following files:\n%s" % ", ".join(failed_files))) # Return mostly empty project_data dict (with just the current version #) log.info("Successfully loaded legacy project file: %s" % file_path) return project_data
def read_legacy_project_file(self, file_path): """Attempt to read a legacy version 1.x openshot project file""" import sys, pickle from classes.query import File, Track, Clip, Transition from classes.app import get_app import openshot try: import json except ImportError: import simplejson as json # Get translation method _ = get_app()._tr # Append version info v = openshot.GetVersion() project_data = {} project_data["version"] = {"openshot-qt" : info.VERSION, "libopenshot" : v.ToString()} # Get FPS from project from classes.app import get_app fps = get_app().project.get(["fps"]) fps_float = float(fps["num"]) / float(fps["den"]) # Import legacy openshot classes (from version 1.X) from classes.legacy.openshot import classes as legacy_classes from classes.legacy.openshot.classes import project as legacy_project from classes.legacy.openshot.classes import sequences as legacy_sequences from classes.legacy.openshot.classes import track as legacy_track from classes.legacy.openshot.classes import clip as legacy_clip from classes.legacy.openshot.classes import keyframe as legacy_keyframe from classes.legacy.openshot.classes import files as legacy_files from classes.legacy.openshot.classes import transition as legacy_transition from classes.legacy.openshot.classes import effect as legacy_effect from classes.legacy.openshot.classes import marker as legacy_marker sys.modules['openshot.classes'] = legacy_classes sys.modules['classes.project'] = legacy_project sys.modules['classes.sequences'] = legacy_sequences sys.modules['classes.track'] = legacy_track sys.modules['classes.clip'] = legacy_clip sys.modules['classes.keyframe'] = legacy_keyframe sys.modules['classes.files'] = legacy_files sys.modules['classes.transition'] = legacy_transition sys.modules['classes.effect'] = legacy_effect sys.modules['classes.marker'] = legacy_marker # Keep track of files that failed to load failed_files = [] with open(file_path.encode('UTF-8'), 'rb') as f: try: # Unpickle legacy openshot project file v1_data = pickle.load(f, fix_imports=True, encoding="UTF-8") file_lookup = {} # Loop through files for item in v1_data.project_folder.items: # Is this item a File (i.e. ignore folders) if isinstance(item, legacy_files.OpenShotFile): # Create file try: clip = openshot.Clip(item.name) reader = clip.Reader() file_data = json.loads(reader.Json(), strict=False) # 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 file.save() # Keep track of new ids and old ids file_lookup[item.unique_id] = file except: # Handle exception quietly msg = ("%s is not a valid video, audio, or image file." % item.name) log.error(msg) failed_files.append(item.name) # Delete all tracks track_list = copy.deepcopy(Track.filter()) for track in track_list: track.delete() # Create new tracks track_counter = 0 for legacy_t in reversed(v1_data.sequences[0].tracks): t = Track() t.data = {"number": track_counter, "y": 0, "label": legacy_t.name} t.save() track_counter += 1 # Loop through clips track_counter = 0 for sequence in v1_data.sequences: for track in reversed(sequence.tracks): for clip in track.clips: # Get associated file for this clip if clip.file_object.unique_id in file_lookup.keys(): file = file_lookup[clip.file_object.unique_id] else: # Skip missing file log.info("Skipping importing missing file: %s" % clip.file_object.unique_id) continue # Create clip 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(), strict=False) new_clip["file_id"] = file.id new_clip["title"] = filename new_clip["image"] = thumb_path # Check for optional start and end attributes new_clip["start"] = clip.start_time new_clip["end"] = clip.end_time new_clip["position"] = clip.position_on_track new_clip["layer"] = track_counter # Clear alpha (if needed) if clip.video_fade_in or clip.video_fade_out: new_clip["alpha"]["Points"] = [] # Video Fade IN if clip.video_fade_in: # Add keyframes start = openshot.Point(round(clip.start_time * fps_float) + 1, 0.0, openshot.BEZIER) start_object = json.loads(start.Json(), strict=False) end = openshot.Point(round((clip.start_time + clip.video_fade_in_amount) * fps_float) + 1, 1.0, openshot.BEZIER) end_object = json.loads(end.Json(), strict=False) new_clip["alpha"]["Points"].append(start_object) new_clip["alpha"]["Points"].append(end_object) # Video Fade OUT if clip.video_fade_out: # Add keyframes start = openshot.Point(round((clip.end_time - clip.video_fade_out_amount) * fps_float) + 1, 1.0, openshot.BEZIER) start_object = json.loads(start.Json(), strict=False) end = openshot.Point(round(clip.end_time * fps_float) + 1, 0.0, openshot.BEZIER) end_object = json.loads(end.Json(), strict=False) new_clip["alpha"]["Points"].append(start_object) new_clip["alpha"]["Points"].append(end_object) # Clear Audio (if needed) if clip.audio_fade_in or clip.audio_fade_out: new_clip["volume"]["Points"] = [] else: p = openshot.Point(1, clip.volume / 100.0, openshot.BEZIER) p_object = json.loads(p.Json(), strict=False) new_clip["volume"] = { "Points" : [p_object]} # Audio Fade IN if clip.audio_fade_in: # Add keyframes start = openshot.Point(round(clip.start_time * fps_float) + 1, 0.0, openshot.BEZIER) start_object = json.loads(start.Json(), strict=False) end = openshot.Point(round((clip.start_time + clip.video_fade_in_amount) * fps_float) + 1, clip.volume / 100.0, openshot.BEZIER) end_object = json.loads(end.Json(), strict=False) new_clip["volume"]["Points"].append(start_object) new_clip["volume"]["Points"].append(end_object) # Audio Fade OUT if clip.audio_fade_out: # Add keyframes start = openshot.Point(round((clip.end_time - clip.video_fade_out_amount) * fps_float) + 1, clip.volume / 100.0, openshot.BEZIER) start_object = json.loads(start.Json(), strict=False) end = openshot.Point(round(clip.end_time * fps_float) + 1, 0.0, openshot.BEZIER) end_object = json.loads(end.Json(), strict=False) new_clip["volume"]["Points"].append(start_object) new_clip["volume"]["Points"].append(end_object) # Save clip clip_object = Clip() clip_object.data = new_clip clip_object.save() # Loop through transitions for trans in track.transitions: # Fix default transition if not trans.resource or not os.path.exists(trans.resource): trans.resource = os.path.join(info.PATH, "transitions", "common", "fade.svg") # Open up QtImageReader for transition Image transition_reader = openshot.QtImageReader(trans.resource) trans_begin_value = 1.0 trans_end_value = -1.0 if trans.reverse: trans_begin_value = -1.0 trans_end_value = 1.0 brightness = openshot.Keyframe() brightness.AddPoint(1, trans_begin_value, openshot.BEZIER) brightness.AddPoint(round(trans.length * fps_float) + 1, trans_end_value, openshot.BEZIER) contrast = openshot.Keyframe(trans.softness * 10.0) # Create transition dictionary transitions_data = { "id": get_app().project.generate_id(), "layer": track_counter, "title": "Transition", "type": "Mask", "position": trans.position_on_track, "start": 0, "end": trans.length, "brightness": json.loads(brightness.Json(), strict=False), "contrast": json.loads(contrast.Json(), strict=False), "reader": json.loads(transition_reader.Json(), strict=False), "replace_image": False } # Save transition t = Transition() t.data = transitions_data t.save() # Increment track counter track_counter += 1 except Exception as ex: # Error parsing legacy contents msg = _("Failed to load project file %(path)s: %(error)s" % {"path": file_path, "error": ex}) log.error(msg) raise Exception(msg) # Show warning if some files failed to load if failed_files: # Throw exception raise Exception(_("Failed to load the following files:\n%s" % ", ".join(failed_files))) # Return mostly empty project_data dict (with just the current version #) log.info("Successfully loaded legacy project file: %s" % file_path) return project_data
def __init__(self, files=None, position=0.0): # 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 settings self.settings = settings.get_settings() # Get translation object self.app = get_app() _ = self.app._tr # Track metrics track_metric_screen("add-to-timeline-screen") # Add custom treeview to window self.treeFiles = TimelineTreeView(self) self.vboxTreeParent.insertWidget(0, self.treeFiles) # Update data in model self.treeFiles.timeline_model.update_model(files) # Refresh view self.treeFiles.refresh_view() # Init start position self.txtStartTime.setValue(position) # Init default image length self.txtImageLength.setValue(self.settings.get("default-image-length")) self.txtImageLength.valueChanged.connect(self.updateTotal) self.cmbTransition.currentIndexChanged.connect(self.updateTotal) self.cmbFade.currentIndexChanged.connect(self.updateTotal) self.txtFadeLength.valueChanged.connect(self.updateTotal) self.txtTransitionLength.valueChanged.connect(self.updateTotal) # Add all tracks to dropdown tracks = Track.filter() for track in reversed(tracks): # Add to dropdown self.cmbTrack.addItem(_('Track %s' % track.data['number']), track.data['number']) # Add all fade options self.cmbFade.addItem(_('None'), None) self.cmbFade.addItem(_('Fade In'), 'Fade In') self.cmbFade.addItem(_('Fade Out'), 'Fade Out') self.cmbFade.addItem(_('Fade In & Out'), 'Fade In & Out') # Add all zoom options self.cmbZoom.addItem(_('None'), None) self.cmbZoom.addItem(_('Random'), 'Random') self.cmbZoom.addItem(_('Zoom In'), 'Zoom In') self.cmbZoom.addItem(_('Zoom Out'), 'Zoom Out') # Add all transitions transitions_dir = os.path.join(info.PATH, "transitions") common_dir = os.path.join(transitions_dir, "common") extra_dir = os.path.join(transitions_dir, "extra") transition_groups = [{"type": "common", "dir": common_dir, "files": os.listdir(common_dir)}, {"type": "extra", "dir": extra_dir, "files": os.listdir(extra_dir)}] self.cmbTransition.addItem(_('None'), None) self.cmbTransition.addItem(_('Random'), 'random') self.transitions = [] for group in transition_groups: type = group["type"] dir = group["dir"] files = group["files"] for filename in sorted(files): path = os.path.join(dir, filename) (fileBaseName, fileExtension) = os.path.splitext(filename) # Skip hidden files (such as .DS_Store, etc...) if filename[0] == "." or "thumbs.db" in filename.lower(): continue # split the name into parts (looking for a number) suffix_number = None name_parts = fileBaseName.split("_") if name_parts[-1].isdigit(): suffix_number = name_parts[-1] # get name of transition trans_name = fileBaseName.replace("_", " ").capitalize() # replace suffix number with placeholder (if any) if suffix_number: trans_name = trans_name.replace(suffix_number, "%s") trans_name = _(trans_name) % suffix_number else: trans_name = _(trans_name) # Check for thumbnail path (in build-in cache) thumb_path = os.path.join(info.IMAGES_PATH, "cache", "{}.png".format(fileBaseName)) # Check built-in cache (if not found) if not os.path.exists(thumb_path): # Check user folder cache thumb_path = os.path.join(info.CACHE_PATH, "{}.png".format(fileBaseName)) # Add item self.transitions.append(path) self.cmbTransition.addItem(QIcon(thumb_path), _(trans_name), path) # Connections self.btnMoveUp.clicked.connect(self.btnMoveUpClicked) self.btnMoveDown.clicked.connect(self.btnMoveDownClicked) self.btnShuffle.clicked.connect(self.btnShuffleClicked) self.btnRemove.clicked.connect(self.btnRemoveClicked) self.btnBox.accepted.connect(self.accept) self.btnBox.rejected.connect(self.reject) # Update total self.updateTotal()
def changed(self, action): # Clear previous rects self.clip_rects.clear() self.clip_rects_selected.clear() self.marker_rects.clear() # Get layer lookup layers = {} for count, layer in enumerate(reversed(sorted(Track.filter()))): layers[layer.data.get('number')] = count # Wait for timeline object and valid scrollbar positions if hasattr(get_app().window, "timeline") and self.scrollbar_position[2] != 0.0: # Get max width of timeline project_duration = get_app().project.get("duration") pixels_per_second = self.width() / project_duration # Determine scale factor vertical_factor = self.height() / len(layers.keys()) for clip in Clip.filter(): # Calculate clip geometry (and cache it) clip_x = (clip.data.get('position', 0.0) * pixels_per_second) clip_y = layers.get(clip.data.get('layer', 0), 0) * vertical_factor clip_width = ( (clip.data.get('end', 0.0) - clip.data.get('start', 0.0)) * pixels_per_second) clip_rect = QRectF(clip_x, clip_y, clip_width, 1.0 * vertical_factor) if clip.id in get_app().window.selected_clips: # selected clip self.clip_rects_selected.append(clip_rect) else: # un-selected clip self.clip_rects.append(clip_rect) for clip in Transition.filter(): # Calculate clip geometry (and cache it) clip_x = (clip.data.get('position', 0.0) * pixels_per_second) clip_y = layers.get(clip.data.get('layer', 0), 0) * vertical_factor clip_width = ( (clip.data.get('end', 0.0) - clip.data.get('start', 0.0)) * pixels_per_second) clip_rect = QRectF(clip_x, clip_y, clip_width, 1.0 * vertical_factor) if clip.id in get_app().window.selected_transitions: # selected clip self.clip_rects_selected.append(clip_rect) else: # un-selected clip self.clip_rects.append(clip_rect) for marker in Marker.filter(): # Calculate clip geometry (and cache it) marker_x = (marker.data.get('position', 0.0) * pixels_per_second) marker_rect = QRectF(marker_x, 0, 0.5, len(layers) * vertical_factor) self.marker_rects.append(marker_rect) # Force re-paint self.update()
def paintEvent(self, event, *args): """ Custom paint event """ event.accept() # Paint timeline preview on QWidget painter = QPainter(self) painter.setRenderHints( QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing, True) # Fill the whole widget with the solid color (background solid color) painter.fillRect(event.rect(), QColor("#191919")) # Create pens / colors clip_pen = QPen(QBrush(QColor("#53a0ed")), 1.5) clip_pen.setCosmetic(True) painter.setPen(clip_pen) selected_clip_pen = QPen(QBrush(QColor("Red")), 1.5) selected_clip_pen.setCosmetic(True) scroll_color = QColor("#4053a0ed") scroll_pen = QPen(QBrush(scroll_color), 2.0) scroll_pen.setCosmetic(True) marker_color = QColor("#4053a0ed") marker_pen = QPen(QBrush(marker_color), 1.0) marker_pen.setCosmetic(True) playhead_color = QColor(Qt.red) playhead_color.setAlphaF(0.5) playhead_pen = QPen(QBrush(playhead_color), 1.0) playhead_pen.setCosmetic(True) handle_color = QColor("#a653a0ed") handle_pen = QPen(QBrush(handle_color), 1.5) handle_pen.setCosmetic(True) # Get layer lookup layers = Track.filter() # Wait for timeline object and valid scrollbar positions if get_app().window.timeline and self.scrollbar_position[2] != 0.0: # Get max width of timeline project_duration = get_app().project.get("duration") pixels_per_second = event.rect().width() / project_duration project_pixel_width = max(0, project_duration * pixels_per_second) scroll_width = (self.scrollbar_position[1] - self.scrollbar_position[0]) * event.rect().width() # Get FPS info fps_num = get_app().project.get("fps").get("num", 24) fps_den = get_app().project.get("fps").get("den", 1) fps_float = float(fps_num / fps_den) # Determine scale factor vertical_factor = event.rect().height() / len(layers) # Loop through each clip painter.setPen(clip_pen) for clip_rect in self.clip_rects: painter.drawRect(clip_rect) painter.setPen(selected_clip_pen) for clip_rect in self.clip_rects_selected: painter.drawRect(clip_rect) painter.setPen(marker_pen) for marker_rect in self.marker_rects: painter.drawRect(marker_rect) painter.setPen(playhead_pen) playhead_x = ((self.current_frame / fps_float) * pixels_per_second) playhead_rect = QRectF(playhead_x, 0, 0.5, len(layers) * vertical_factor) painter.drawRect(playhead_rect) # Draw scroll bars (if available) if self.scrollbar_position: painter.setPen(scroll_pen) # scroll bar path scroll_x = self.scrollbar_position[0] * event.rect().width() self.scroll_bar_rect = QRectF(scroll_x, 0.0, scroll_width, event.rect().height()) scroll_path = QPainterPath() scroll_path.addRoundedRect(self.scroll_bar_rect, 6, 6) # draw scroll bar rect painter.fillPath(scroll_path, scroll_color) painter.drawPath(scroll_path) # draw handles painter.setPen(handle_pen) handle_width = 12.0 # left handle left_handle_x = (self.scrollbar_position[0] * event.rect().width()) - (handle_width / 2.0) self.left_handle_rect = QRectF(left_handle_x, event.rect().height() / 4.0, handle_width, event.rect().height() / 2.0) left_handle_path = QPainterPath() left_handle_path.addRoundedRect(self.left_handle_rect, handle_width, handle_width) painter.fillPath(left_handle_path, handle_color) # right handle right_handle_x = (self.scrollbar_position[1] * event.rect().width()) - (handle_width / 2.0) self.right_handle_rect = QRectF(right_handle_x, event.rect().height() / 4.0, handle_width, event.rect().height() / 2.0) right_handle_path = QPainterPath() right_handle_path.addRoundedRect(self.right_handle_rect, handle_width, handle_width) painter.fillPath(right_handle_path, handle_color) # Determine if play-head is inside scroll area if get_app().window.preview_thread.player.Mode( ) == openshot.PLAYBACK_PLAY and self.is_auto_center: if not self.scroll_bar_rect.contains(playhead_rect): get_app().window.TimelineCenter.emit() # End painter painter.end()