diff options
author | Nathan Lovato <nathan@gdquest.com> | 2020-02-09 04:59:08 +0300 |
---|---|---|
committer | Nathan Lovato <nathan@gdquest.com> | 2020-02-13 18:19:35 +0300 |
commit | cf9fde2568aa81197d0f36019ee429880430906d (patch) | |
tree | b74875c507001fa41ca979bc689cfd4a0dee0f13 /power_sequencer/operators | |
parent | 47d56e88240dc330e9173c668f1ee9fee39fb36c (diff) |
power_sequencer: update to the latest master
This commit brings Power Sequencer to the current rolling version, that is
commit 9562eb58d164e234f61225250d6ac5ca858ada7b on
https://github.com/GDQuest/blender-power-sequencer/.
I had already made an intermediate commit that added new features and fixed some
bugs. This commit brings bug fixes and many quality of life improvements.
- Human-readable changelog:
https://github.com/GDQuest/blender-power-sequencer/blob/master/CHANGELOG.md
- 1.4.0 release post:
https://github.com/GDQuest/blender-power-sequencer/releases/tag/1.4.0
- All the commits since v1.3.0 (previous update + this one in this repository):
https://github.com/GDQuest/blender-power-sequencer/compare/1.3.0...master
Diffstat (limited to 'power_sequencer/operators')
34 files changed, 240 insertions, 551 deletions
diff --git a/power_sequencer/operators/__init__.py b/power_sequencer/operators/__init__.py index 93519426..535b29cd 100644..100755 --- a/power_sequencer/operators/__init__.py +++ b/power_sequencer/operators/__init__.py @@ -14,143 +14,25 @@ # You should have received a copy of the GNU General Public License along with Power Sequencer. If # not, see <https://www.gnu.org/licenses/>. # -from .speed_up_movie_strip import POWER_SEQUENCER_OT_speed_up_movie_strip -from .align_audios import POWER_SEQUENCER_OT_align_audios -from .playback_speed_set import POWER_SEQUENCER_OT_playback_speed_set -from .channel_offset import POWER_SEQUENCER_OT_channel_offset -from .concatenate_strips import POWER_SEQUENCER_OT_concatenate_strips -from .copy_selected_sequences import POWER_SEQUENCER_OT_copy_selected_sequences -from .crossfade_add import POWER_SEQUENCER_OT_crossfade_add -from .crossfade_edit import POWER_SEQUENCER_OT_crossfade_edit -from .transitions_remove import POWER_SEQUENCER_OT_transitions_remove -from .cut_strips_under_cursor import POWER_SEQUENCER_OT_split_strips_under_cursor -from .playback_speed_decrease import POWER_SEQUENCER_OT_playback_speed_decrease -from .delete_direct import POWER_SEQUENCER_OT_delete_direct -from .deselect_all_left_or_right import POWER_SEQUENCER_OT_deselect_all_strips_left_or_right -from .deselect_handles_and_grab import POWER_SEQUENCER_OT_deselect_handles_and_grab -from .duplicate_move import POWER_SEQUENCER_OT_duplicate_move -from .expand_to_surrounding_cuts import POWER_SEQUENCER_OT_expand_to_surrounding_cuts -from .fade_add import POWER_SEQUENCER_OT_fade_add -from .fade_clear import POWER_SEQUENCER_OT_fade_clear -from .grab_closest_handle_or_cut import POWER_SEQUENCER_OT_grab_closest_cut -from .grab import POWER_SEQUENCER_OT_grab -from .grab_sequence_handles import POWER_SEQUENCER_OT_grab_sequence_handles -from .import_local_footage import POWER_SEQUENCER_OT_import_local_footage -from .playback_speed_increase import POWER_SEQUENCER_OT_playback_speed_increase -from .jump_time_offset import POWER_SEQUENCER_OT_jump_time_offset -from .jump_to_cut import POWER_SEQUENCER_OT_jump_to_cut -from .make_still_image import POWER_SEQUENCER_OT_make_still_image -from .marker_delete_closest import POWER_SEQUENCER_OT_marker_delete_closest -from .marker_delete_direct import POWER_SEQUENCER_OT_marker_delete_direct -from .marker_go_to_next import POWER_SEQUENCER_OT_marker_go_to_next -from .markers_as_timecodes import POWER_SEQUENCER_OT_copy_markers_as_timecodes -from .markers_create_from_selected import POWER_SEQUENCER_OT_markers_create_from_selected_strips -from .marker_snap_to_cursor import POWER_SEQUENCER_OT_marker_snap_to_cursor -from .markers_snap_matching_strips import POWER_SEQUENCER_OT_markers_snap_matching_strips -from .meta_resize_to_content import POWER_SEQUENCER_OT_meta_resize_to_content -from .meta_ungroup_and_trim import POWER_SEQUENCER_OT_meta_ungroup_and_trim -from .meta_trim_content_to_bounds import POWER_SEQUENCER_OT_meta_trim_content_to_bounds -from .mouse_trim_modal import POWER_SEQUENCER_OT_mouse_trim -from .space_sequences import POWER_SEQUENCER_OT_space_sequences -from .mouse_toggle_mute import POWER_SEQUENCER_OT_mouse_toggle_mute -from .mouse_trim_instantly import POWER_SEQUENCER_OT_mouse_trim_instantly -from .open_project_directory import POWER_SEQUENCER_OT_open_project_directory -from .preview_closest_cut import POWER_SEQUENCER_OT_preview_closest_cut -from .preview_to_selection import POWER_SEQUENCER_OT_preview_to_selection -from .gap_remove import POWER_SEQUENCER_OT_gap_remove -from .scene_rename_with_strip import POWER_SEQUENCER_OT_scene_rename_with_strip -from .render_apply_preset import POWER_SEQUENCER_OT_render_apply_preset -from .ripple_delete import POWER_SEQUENCER_OT_ripple_delete -from .save_direct import POWER_SEQUENCER_OT_save_direct -from .scene_create_from_selection import POWER_SEQUENCER_OT_scene_create_from_selection -from .scene_cycle import POWER_SEQUENCER_OT_scene_cycle -from .select_closest_to_mouse import POWER_SEQUENCER_OT_select_closest_to_mouse -from .select_linked_strips import POWER_SEQUENCER_OT_select_linked_strips -from .select_linked_effect import POWER_SEQUENCER_OT_select_linked_effect -from .select_related_strips import POWER_SEQUENCER_OT_select_related_strips -from .select_strips_under_cursor import POWER_SEQUENCER_OT_select_strips_under_cursor -from .markers_set_preview_in_between import POWER_SEQUENCER_OT_set_preview_between_markers -from .set_timeline_range import POWER_SEQUENCER_OT_set_timeline_range -from .trim_left_or_right_handles import POWER_SEQUENCER_OT_trim_left_or_right_handles -from .snap import POWER_SEQUENCER_OT_snap -from .snap_selection import POWER_SEQUENCER_OT_snap_selection -from .speed_remove_effect import POWER_SEQUENCER_OT_speed_remove_effect -from .swap_strips import POWER_SEQUENCER_OT_swap_strips -from .select_all_left_or_right import POWER_SEQUENCER_OT_select_all_left_or_right -from .toggle_selected_mute import POWER_SEQUENCER_OT_toggle_selected_mute -from .toggle_waveforms import POWER_SEQUENCER_OT_toggle_waveforms -from .trim_three_point_edit import POWER_SEQUENCER_OT_trim_three_point_edit -from .trim_to_surrounding_cuts import POWER_SEQUENCER_OT_trim_to_surrounding_cuts +import importlib +import os + + +def get_operator_classes(): + """Returns the list of operators in the add-on""" + this_file = os.path.dirname(__file__) + module_files = [ + f for f in os.listdir(this_file) if f.endswith(".py") and not f.startswith("__init__") + ] + module_paths = ["." + os.path.splitext(f)[0] for f in module_files] + classes = [] + print(__name__) + for path in module_paths: + module = importlib.import_module(path, package="blender_power_sequencer.operators") + operator_names = [entry for entry in dir(module) if entry.startswith("POWER_SEQUENCER_OT")] + classes.extend([getattr(module, name) for name in operator_names]) + return classes -classes = [ - POWER_SEQUENCER_OT_speed_up_movie_strip, - POWER_SEQUENCER_OT_align_audios, - POWER_SEQUENCER_OT_playback_speed_set, - POWER_SEQUENCER_OT_channel_offset, - POWER_SEQUENCER_OT_concatenate_strips, - POWER_SEQUENCER_OT_copy_selected_sequences, - POWER_SEQUENCER_OT_crossfade_add, - POWER_SEQUENCER_OT_crossfade_edit, - POWER_SEQUENCER_OT_transitions_remove, - POWER_SEQUENCER_OT_split_strips_under_cursor, - POWER_SEQUENCER_OT_playback_speed_decrease, - POWER_SEQUENCER_OT_delete_direct, - POWER_SEQUENCER_OT_deselect_all_strips_left_or_right, - POWER_SEQUENCER_OT_deselect_handles_and_grab, - POWER_SEQUENCER_OT_duplicate_move, - POWER_SEQUENCER_OT_expand_to_surrounding_cuts, - POWER_SEQUENCER_OT_fade_add, - POWER_SEQUENCER_OT_fade_clear, - POWER_SEQUENCER_OT_grab_closest_cut, - POWER_SEQUENCER_OT_grab, - POWER_SEQUENCER_OT_grab_sequence_handles, - POWER_SEQUENCER_OT_import_local_footage, - POWER_SEQUENCER_OT_playback_speed_increase, - POWER_SEQUENCER_OT_jump_time_offset, - POWER_SEQUENCER_OT_jump_to_cut, - POWER_SEQUENCER_OT_make_still_image, - POWER_SEQUENCER_OT_marker_delete_closest, - POWER_SEQUENCER_OT_marker_delete_direct, - POWER_SEQUENCER_OT_marker_go_to_next, - POWER_SEQUENCER_OT_copy_markers_as_timecodes, - POWER_SEQUENCER_OT_markers_create_from_selected_strips, - POWER_SEQUENCER_OT_marker_snap_to_cursor, - POWER_SEQUENCER_OT_markers_snap_matching_strips, - POWER_SEQUENCER_OT_meta_resize_to_content, - POWER_SEQUENCER_OT_meta_ungroup_and_trim, - POWER_SEQUENCER_OT_meta_trim_content_to_bounds, - POWER_SEQUENCER_OT_mouse_trim, - POWER_SEQUENCER_OT_space_sequences, - POWER_SEQUENCER_OT_mouse_toggle_mute, - POWER_SEQUENCER_OT_mouse_trim_instantly, - POWER_SEQUENCER_OT_open_project_directory, - POWER_SEQUENCER_OT_preview_closest_cut, - POWER_SEQUENCER_OT_preview_to_selection, - POWER_SEQUENCER_OT_gap_remove, - POWER_SEQUENCER_OT_scene_rename_with_strip, - POWER_SEQUENCER_OT_render_apply_preset, - POWER_SEQUENCER_OT_ripple_delete, - POWER_SEQUENCER_OT_save_direct, - POWER_SEQUENCER_OT_scene_create_from_selection, - POWER_SEQUENCER_OT_scene_cycle, - POWER_SEQUENCER_OT_select_closest_to_mouse, - POWER_SEQUENCER_OT_select_linked_strips, - POWER_SEQUENCER_OT_select_linked_effect, - POWER_SEQUENCER_OT_select_related_strips, - POWER_SEQUENCER_OT_select_strips_under_cursor, - POWER_SEQUENCER_OT_set_preview_between_markers, - POWER_SEQUENCER_OT_set_timeline_range, - POWER_SEQUENCER_OT_trim_left_or_right_handles, - POWER_SEQUENCER_OT_snap, - POWER_SEQUENCER_OT_snap_selection, - POWER_SEQUENCER_OT_speed_remove_effect, - POWER_SEQUENCER_OT_swap_strips, - POWER_SEQUENCER_OT_toggle_selected_mute, - POWER_SEQUENCER_OT_toggle_waveforms, - POWER_SEQUENCER_OT_trim_three_point_edit, - POWER_SEQUENCER_OT_select_all_left_or_right, - POWER_SEQUENCER_OT_trim_to_surrounding_cuts, -] doc = { "sequencer.refresh_all": { diff --git a/power_sequencer/operators/audiosync/convert_and_trim.py b/power_sequencer/operators/audiosync/convert_and_trim.py index 1ce1a4c1..859186d8 100644 --- a/power_sequencer/operators/audiosync/convert_and_trim.py +++ b/power_sequencer/operators/audiosync/convert_and_trim.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU General Public License along with Power Sequencer. If # not, see <https://www.gnu.org/licenses/>. # -import os import subprocess import tempfile diff --git a/power_sequencer/operators/audiosync/find_offset.py b/power_sequencer/operators/audiosync/find_offset.py index 90153e1a..35b17a14 100644 --- a/power_sequencer/operators/audiosync/find_offset.py +++ b/power_sequencer/operators/audiosync/find_offset.py @@ -18,7 +18,6 @@ This code is an adaptation of 'audio-offset-finder' by BBC """ import os -import warnings import numpy as np from .mfcc import mfcc diff --git a/power_sequencer/operators/audiosync/mfcc/mfcc.py b/power_sequencer/operators/audiosync/mfcc/mfcc.py index 5d9c9467..7f90a5b1 100644 --- a/power_sequencer/operators/audiosync/mfcc/mfcc.py +++ b/power_sequencer/operators/audiosync/mfcc/mfcc.py @@ -16,6 +16,10 @@ # import numpy as np +from scipy.signal import hamming, lfilter +from scipy.fftpack import fft +from scipy.fftpack.realtransforms import dct + from .trfbank import trfbank from .segment_axis import segment_axis @@ -52,10 +56,6 @@ def mfcc(input, nwin=256, nfft=512, fs=16000, nceps=13): spoken sentences", IEEE Trans. Acoustics. Speech, Signal Proc. ASSP-28 (4): 357-366, August 1980.""" - from scipy.signal import hamming, lfilter - from scipy.fftpack import fft - from scipy.fftpack.realtransforms import dct - # MFCC parameters: taken from auditory toolbox over = nwin - 160 # Pre-emphasis factor (to take into account the -6dB/octave rolloff of the @@ -70,7 +70,6 @@ def mfcc(input, nwin=256, nfft=512, fs=16000, nceps=13): nlinfil = 13 nlogfil = 27 - nfil = nlinfil + nlogfil w = hamming(nwin, sym=0) diff --git a/power_sequencer/operators/concatenate_strips.py b/power_sequencer/operators/concatenate_strips.py index 4ef2407d..11dff248 100644 --- a/power_sequencer/operators/concatenate_strips.py +++ b/power_sequencer/operators/concatenate_strips.py @@ -18,7 +18,11 @@ import bpy from operator import attrgetter from .utils.global_settings import SequenceTypes -from .utils.functions import find_sequences_after, get_mouse_frame_and_channel, sequencer_workaround_2_80_audio_bug +from .utils.functions import ( + find_sequences_after, + get_mouse_frame_and_channel, + ripple_move, +) from .utils.doc import doc_name, doc_idname, doc_brief, doc_description @@ -45,22 +49,22 @@ class POWER_SEQUENCER_OT_concatenate_strips(bpy.types.Operator): "shortcuts": [ ( {"type": "C", "value": "PRESS"}, - {"concatenate_all": False, "to_left": True}, + {"concatenate_all": False, "is_towards_left": True}, ("Concatenate and select the next strip in the channel"), ), ( {"type": "C", "value": "PRESS", "shift": True}, - {"concatenate_all": True, "to_left": True}, + {"concatenate_all": True, "is_towards_left": True}, "Concatenate all strips in selected channels", ), ( {"type": "C", "value": "PRESS", "alt": True}, - {"concatenate_all": False, "to_left": False}, + {"concatenate_all": False, "is_towards_left": False}, ("Concatenate and select the previous strip in the channel towards the right"), ), ( {"type": "C", "value": "PRESS", "shift": True, "alt": True}, - {"concatenate_all": True, "to_left": False}, + {"concatenate_all": True, "is_towards_left": False}, "Shift Alt C; Concatenate all strips in channel towards the right", ), ], @@ -76,11 +80,16 @@ class POWER_SEQUENCER_OT_concatenate_strips(bpy.types.Operator): description=("If only one strip selected, concatenate" " the entire channel"), default=False, ) - to_left: bpy.props.BoolProperty( + is_towards_left: bpy.props.BoolProperty( name="To Left", description="Concatenate strips moving them back in time (default) or forward in time", default=True, ) + do_ripple: bpy.props.BoolProperty( + name="Ripple Edit", + description="Ripple the time offset to strips after the concatenated one", + default=False, + ) @classmethod def poll(cls, context): @@ -96,11 +105,12 @@ class POWER_SEQUENCER_OT_concatenate_strips(bpy.types.Operator): selection = context.selected_sequences channels = {s.channel for s in selection} - if len(selection) == len(channels): + is_one_strip_per_channel = len(selection) == len(channels) + if is_one_strip_per_channel: for s in selection: candidates = ( find_sequences_before(context, s) - if not self.to_left + if not self.is_towards_left else find_sequences_after(context, s) ) to_concatenate = [ @@ -110,45 +120,53 @@ class POWER_SEQUENCER_OT_concatenate_strips(bpy.types.Operator): and not strip.lock and strip.type in SequenceTypes.CUTABLE ] - self.concatenate(s, to_concatenate) + if not to_concatenate: + continue + self.concatenate(context, s, to_concatenate) else: for channel in channels: to_concatenate = [s for s in selection if s.channel == channel] strip_target = ( min(to_concatenate, key=lambda s: s.frame_final_start) - if self.to_left + if self.is_towards_left else max(to_concatenate, key=lambda s: s.frame_final_start) ) to_concatenate.remove(strip_target) - self.concatenate(strip_target, to_concatenate, force_all=True) + self.concatenate(context, strip_target, to_concatenate, force_all=True) - sequencer_workaround_2_80_audio_bug(context) return {"FINISHED"} - def concatenate(self, strip_target, sequences, force_all=False): + def concatenate(self, context, strip_target, sequences, force_all=False): to_concatenate = sorted(sequences, key=attrgetter("frame_final_start")) - to_concatenate = list(reversed(to_concatenate)) if not self.to_left else to_concatenate + to_concatenate = ( + list(reversed(to_concatenate)) if not self.is_towards_left else to_concatenate + ) to_concatenate = ( [to_concatenate[0]] if not (self.concatenate_all or force_all) else to_concatenate ) - attribute_target = "frame_final_end" if self.to_left else "frame_final_start" - attribute_concat = "frame_final_start" if self.to_left else "frame_final_end" + attribute_target = "frame_final_end" if self.is_towards_left else "frame_final_start" + attribute_concat = "frame_final_start" if self.is_towards_left else "frame_final_end" concatenate_start = getattr(strip_target, attribute_target) last_gap = 0 for s in to_concatenate: if isinstance(s, bpy.types.EffectSequence): concatenate_start = ( - s.frame_final_end - last_gap if self.to_left else s.frame_final_start - last_gap + s.frame_final_end - last_gap + if self.is_towards_left + else s.frame_final_start - last_gap ) continue concat_strip_frame = getattr(s, attribute_concat) gap = concat_strip_frame - concatenate_start - s.frame_start -= gap - concatenate_start = s.frame_final_end if self.to_left else s.frame_final_start + if self.do_ripple and self.is_towards_left: + ripple_move(context, [s], -gap) + else: + s.frame_start -= gap + concatenate_start = s.frame_final_end if self.is_towards_left else s.frame_final_start last_gap = gap - if not self.concatenate_all: + if not (self.concatenate_all or force_all): strip_target.select = False to_concatenate[0].select = True diff --git a/power_sequencer/operators/crossfade_add.py b/power_sequencer/operators/crossfade_add.py index 31f97af2..11558e56 100644 --- a/power_sequencer/operators/crossfade_add.py +++ b/power_sequencer/operators/crossfade_add.py @@ -26,7 +26,6 @@ class POWER_SEQUENCER_OT_crossfade_add(bpy.types.Operator): """ *brief* Adds cross fade between selected sequence and the closest sequence to its right - Based on the active strip, finds the closest next sequence of a similar type, moves it so it overlaps the active strip, and adds a gamma cross effect between them. Works with MOVIE, IMAGE and META strips @@ -66,7 +65,7 @@ class POWER_SEQUENCER_OT_crossfade_add(bpy.types.Operator): sorted_selection = sorted(context.selected_sequences, key=lambda s: s.frame_final_start) for s in sorted_selection: s_next = self.get_next_sequence_after(context, s) - s_to_offset = s_next if s_next.type not in SequenceTypes.EFFECT else s_next.input_1 + s_to_offset = s_next.input_1 if hasattr(s_next, "input_1") else s_next if self.auto_move_strip: offset = s_to_offset.frame_final_start - s.frame_final_end diff --git a/power_sequencer/operators/crossfade_edit.py b/power_sequencer/operators/crossfade_edit.py index 127ab603..553251c9 100644 --- a/power_sequencer/operators/crossfade_edit.py +++ b/power_sequencer/operators/crossfade_edit.py @@ -48,7 +48,6 @@ class POWER_SEQUENCER_OT_crossfade_edit(bpy.types.Operator): def poll(cls, context): return context.scene.sequence_editor.active_strip and context.selected_sequences - def execute(self, context): active = context.scene.sequence_editor.active_strip if active.type not in self.crossfade_types: diff --git a/power_sequencer/operators/expand_to_surrounding_cuts.py b/power_sequencer/operators/expand_to_surrounding_cuts.py index 34507da8..852268bb 100644 --- a/power_sequencer/operators/expand_to_surrounding_cuts.py +++ b/power_sequencer/operators/expand_to_surrounding_cuts.py @@ -15,11 +15,9 @@ # not, see <https://www.gnu.org/licenses/>. # import bpy -from math import floor -from .utils.functions import convert_duration_to_frames -from .utils.doc import doc_name, doc_idname, doc_brief, doc_description from .utils.functions import slice_selection +from .utils.doc import doc_name, doc_idname, doc_brief, doc_description class POWER_SEQUENCER_OT_expand_to_surrounding_cuts(bpy.types.Operator): @@ -63,16 +61,15 @@ class POWER_SEQUENCER_OT_expand_to_surrounding_cuts(bpy.types.Operator): def invoke(self, context, event): sequence_blocks = slice_selection(context, context.selected_sequences) for sequences in sequence_blocks: - sequences_frame_start = min(sequences, key=lambda s: s.frame_final_start).frame_final_start + sequences_frame_start = min( + sequences, key=lambda s: s.frame_final_start + ).frame_final_start sequences_frame_end = max(sequences, key=lambda s: s.frame_final_end).frame_final_end frame_left, frame_right = find_closest_cuts( context, sequences_frame_start, sequences_frame_end ) - if ( - sequences_frame_start == frame_left - and sequences_frame_end == frame_right - ): + if sequences_frame_start == frame_left and sequences_frame_end == frame_right: continue to_extend_left = [s for s in sequences if s.frame_final_start == sequences_frame_start] @@ -80,15 +77,11 @@ class POWER_SEQUENCER_OT_expand_to_surrounding_cuts(bpy.types.Operator): for s in to_extend_left: s.frame_final_start = ( - frame_left - if frame_left < sequences_frame_start - else sequences_frame_start + frame_left if frame_left < sequences_frame_start else sequences_frame_start ) for s in to_extend_right: s.frame_final_end = ( - frame_right - if frame_right > sequences_frame_end - else sequences_frame_end + frame_right if frame_right > sequences_frame_end else sequences_frame_end ) return {"FINISHED"} diff --git a/power_sequencer/operators/fade_add.py b/power_sequencer/operators/fade_add.py index 2053a77c..d72f034d 100644 --- a/power_sequencer/operators/fade_add.py +++ b/power_sequencer/operators/fade_add.py @@ -27,7 +27,7 @@ class POWER_SEQUENCER_OT_fade_add(bpy.types.Operator): Fade options: - In, Out, In and Out create a fade animation of the given duration from - the start of the sequence, to the end of the sequence, or on both sides + the start of the sequence, to the end of the sequence, or on boths sides - From playhead: the fade animation goes from the start of sequences under the playhead to the playhead - To playhead: the fade animation goes from the playhead to the end of sequences under the playhead diff --git a/power_sequencer/operators/fade_clear.py b/power_sequencer/operators/fade_clear.py index d7abb4d7..ebccbe23 100644 --- a/power_sequencer/operators/fade_clear.py +++ b/power_sequencer/operators/fade_clear.py @@ -17,7 +17,6 @@ import bpy from .utils.doc import doc_name, doc_idname, doc_brief, doc_description -from .utils.global_settings import SequenceTypes class POWER_SEQUENCER_OT_fade_clear(bpy.types.Operator): @@ -51,14 +50,15 @@ class POWER_SEQUENCER_OT_fade_clear(bpy.types.Operator): for sequence in context.selected_sequences: animated_property = "volume" if hasattr(sequence, "volume") else "blend_alpha" - for curve in fcurves: - if not curve.data_path.endswith(animated_property): - continue - # Ensure the fcurve corresponds to the selected sequence - if sequence == eval( - "bpy.context.scene." + curve.data_path.replace("." + animated_property, "") - ): - fcurves.remove(curve) + data_path = sequence.path_from_id() + "." + animated_property + fcurve_map = { + curve.data_path: curve + for curve in fcurves + if curve.data_path.startswith("sequence_editor.sequences_all") + } + curve = fcurve_map.get(data_path) + if curve: + fcurves.remove(curve) setattr(sequence, animated_property, 1.0) return {"FINISHED"} diff --git a/power_sequencer/operators/gap_remove.py b/power_sequencer/operators/gap_remove.py index 8ef00b6b..88a3484a 100644 --- a/power_sequencer/operators/gap_remove.py +++ b/power_sequencer/operators/gap_remove.py @@ -17,8 +17,8 @@ import bpy from operator import attrgetter -from .utils.functions import slice_selection, sequencer_workaround_2_80_audio_bug from .utils.doc import doc_name, doc_idname, doc_brief, doc_description +from .utils.functions import slice_selection class POWER_SEQUENCER_OT_gap_remove(bpy.types.Operator): @@ -84,7 +84,6 @@ class POWER_SEQUENCER_OT_gap_remove(bpy.types.Operator): ) self.gaps_remove(context, blocks_after_gap, gap_frame) - sequencer_workaround_2_80_audio_bug(context) return {"FINISHED"} def find_gap_frame(self, context, frame, sorted_sequences): diff --git a/power_sequencer/operators/grab.py b/power_sequencer/operators/grab.py index af8109ff..dd198668 100644 --- a/power_sequencer/operators/grab.py +++ b/power_sequencer/operators/grab.py @@ -56,11 +56,11 @@ class POWER_SEQUENCER_OT_grab(bpy.types.Operator): def execute(self, context): if len(context.selected_sequences) == 0: - return {'FINISHED'} + return {"FINISHED"} strip = context.selected_sequences[0] if len(context.selected_sequences) == 1 and strip.type in SequenceTypes.TRANSITION: context.scene.sequence_editor.active_strip = strip return bpy.ops.power_sequencer.crossfade_edit() else: - return bpy.ops.transform.seq_slide('INVOKE_DEFAULT') + return bpy.ops.transform.seq_slide("INVOKE_DEFAULT") diff --git a/power_sequencer/operators/import_local_footage.py b/power_sequencer/operators/import_local_footage.py index 1f13b15d..5ffee493 100644 --- a/power_sequencer/operators/import_local_footage.py +++ b/power_sequencer/operators/import_local_footage.py @@ -17,7 +17,6 @@ import json import os import re -from operator import attrgetter import bpy @@ -91,7 +90,9 @@ class POWER_SEQUENCER_OT_import_local_footage(bpy.types.Operator): self.directory = os.path.split(bpy.data.filepath)[0] filepaths = self.find_local_footage_files() - files_to_import = [os.path.join(self.directory, f) for f in self.find_new_files_to_import(filepaths)] + files_to_import = [ + os.path.join(self.directory, f) for f in self.find_new_files_to_import(filepaths) + ] print(files_to_import) if not files_to_import: self.report({"INFO"}, "No new files to import found") @@ -162,7 +163,7 @@ class POWER_SEQUENCER_OT_import_local_footage(bpy.types.Operator): Creates a new text data block that contains an empty json list, renames it to `name` and returns it """ - re_text = re.compile(r"^Text.[0-9]{3}$") + re.compile(r"^Text.[0-9]{3}$") bpy.ops.text.new() diff --git a/power_sequencer/operators/marker_go_to_next.py b/power_sequencer/operators/marker_go_to_next.py deleted file mode 100644 index 014091e5..00000000 --- a/power_sequencer/operators/marker_go_to_next.py +++ /dev/null @@ -1,74 +0,0 @@ -# -# Copyright (C) 2016-2019 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors -# -# This file is part of Power Sequencer. -# -# Power Sequencer is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# Power Sequencer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with Power Sequencer. If -# not, see <https://www.gnu.org/licenses/>. -# -import bpy - -from .utils.functions import find_neighboring_markers -from .utils.doc import doc_name, doc_idname, doc_brief, doc_description - - -class POWER_SEQUENCER_OT_marker_go_to_next(bpy.types.Operator): - """ - Moves the time cursor to the next marker - """ - - doc = { - "name": doc_name(__qualname__), - "demo": "", - "description": doc_description(__doc__), - "shortcuts": [], - "keymap": "Sequencer", - } - bl_idname = doc_idname(__qualname__) - bl_label = doc["name"] - bl_description = doc_brief(doc["description"]) - bl_options = {"REGISTER", "UNDO"} - - target_marker: bpy.props.EnumProperty( - items=[("left", "left", "left"), ("right", "right", "right")], - name="Target marker", - description="Move to the closest marker to the left or to the right of the cursor", - default="left", - ) - - @classmethod - def poll(cls, context): - return context.scene - - def execute(self, context): - if not context.scene.timeline_markers: - self.report({"ERROR_INVALID_INPUT"}, "There are no markers. Operation cancelled.") - return {"CANCELLED"} - - frame = context.scene.frame_current - previous_marker, next_marker = find_neighboring_markers(context, frame) - - if ( - not previous_marker - and self.target_marker == "left" - or not next_marker - and self.target_marker == "right" - ): - self.report({"INFO"}, "No more markers to jump to on the %s side." % self.target_marker) - return {"CANCELLED"} - - previous_time = previous_marker.frame if previous_marker else None - next_time = next_marker.frame if next_marker else None - - context.scene.frame_current = ( - previous_time if self.target_marker == "left" or not next_time else next_time - ) - return {"FINISHED"} diff --git a/power_sequencer/operators/markers_as_timecodes.py b/power_sequencer/operators/markers_as_timecodes.py index 642d6118..6fdfd19d 100644 --- a/power_sequencer/operators/markers_as_timecodes.py +++ b/power_sequencer/operators/markers_as_timecodes.py @@ -16,7 +16,6 @@ # import bpy import datetime as dt -import operator as op from .utils.doc import doc_name, doc_idname, doc_brief, doc_description @@ -48,8 +47,7 @@ class POWER_SEQUENCER_OT_copy_markers_as_timecodes(bpy.types.Operator): self.report({"INFO"}, "No markers found") return {"CANCELLED"} - sorted_markers = sorted(context.scene.timeline_markers, - key=lambda m: m.frame) + sorted_markers = sorted(context.scene.timeline_markers, key=lambda m: m.frame) framerate = render.fps / render.fps_base last_marker_seconds = sorted_markers[-1].frame / framerate @@ -59,9 +57,9 @@ class POWER_SEQUENCER_OT_copy_markers_as_timecodes(bpy.types.Operator): markers_as_timecodes = [] for marker in sorted_markers: time = dt.datetime(year=1, month=1, day=1) + dt.timedelta( - seconds=marker.frame / framerate) - markers_as_timecodes.append( - time.strftime(time_format) + " " + marker.name) + seconds=marker.frame / framerate + ) + markers_as_timecodes.append(time.strftime(time_format) + " " + marker.name) bpy.context.window_manager.clipboard = "\n".join(markers_as_timecodes) return {"FINISHED"} diff --git a/power_sequencer/operators/mouse_trim_instantly.py b/power_sequencer/operators/mouse_trim_instantly.py index dd2bb1fd..aeba2325 100644 --- a/power_sequencer/operators/mouse_trim_instantly.py +++ b/power_sequencer/operators/mouse_trim_instantly.py @@ -21,7 +21,6 @@ from .utils.functions import find_strips_mouse from .utils.functions import trim_strips from .utils.functions import get_frame_range from .utils.doc import doc_name, doc_idname, doc_brief, doc_description -from .utils.functions import sequencer_workaround_2_80_audio_bug class POWER_SEQUENCER_OT_mouse_trim_instantly(bpy.types.Operator): @@ -79,35 +78,33 @@ class POWER_SEQUENCER_OT_mouse_trim_instantly(bpy.types.Operator): default=True, ) - to_select = [] - @classmethod def poll(cls, context): return context.sequences def invoke(self, context, event): - to_select = [] + to_trim = [] frame, channel = -1, -1 - x, y = context.region.view2d.region_to_view( - x=event.mouse_region_x, y=event.mouse_region_y - ) + x, y = context.region.view2d.region_to_view(x=event.mouse_region_x, y=event.mouse_region_y) frame, channel = round(x), floor(y) mouse_clicked_strip = find_strips_mouse(context, frame, channel, self.select_linked) - to_select.extend(mouse_clicked_strip) - if self.select_mode == "CURSOR": - to_select.extend([s for s in context.sequences if s.frame_final_start <= frame <= s.frame_final_end and not s.lock]) - - frame_cut_closest = min(get_frame_range(context, to_select), key=lambda f: abs(frame - f)) + to_trim += mouse_clicked_strip + if self.select_mode == "CURSOR" or (self.select_mode == "CONTEXT" and to_trim == []): + to_trim += [ + s + for s in context.sequences + if s.frame_final_start <= frame <= s.frame_final_end and not s.lock + ] + + frame_cut_closest = min(get_frame_range(context, to_trim), key=lambda f: abs(frame - f)) frame_start = min(frame, frame_cut_closest) frame_end = max(frame, frame_cut_closest) - trim_strips(context, frame_start, frame_end, self.select_mode, to_select) + trim_strips(context, frame_start, frame_end, to_trim=to_trim) context.scene.frame_current = frame_start if self.gap_remove and self.select_mode == "CURSOR": bpy.ops.power_sequencer.gap_remove() - # FIXME: Workaround Blender 2.80's audio bug, remove when fixed in Blender - sequencer_workaround_2_80_audio_bug(context) return {"FINISHED"} diff --git a/power_sequencer/operators/mouse_trim_modal.py b/power_sequencer/operators/mouse_trim_modal.py index 4e4bb407..b75985b7 100644 --- a/power_sequencer/operators/mouse_trim_modal.py +++ b/power_sequencer/operators/mouse_trim_modal.py @@ -17,7 +17,6 @@ import bpy import bgl import gpu -from gpu_extras.batch import batch_for_shader import math from mathutils import Vector @@ -26,7 +25,6 @@ from .utils.functions import ( trim_strips, find_snap_candidate, find_closest_surrounding_cuts, - sequencer_workaround_2_80_audio_bug, ) from .utils.draw import ( @@ -48,7 +46,7 @@ class POWER_SEQUENCER_OT_mouse_trim(bpy.types.Operator): *brief* Cut or Trim strips quickly with the mouse cursor - Click somewehre in the Sequencer to insert a cut, click and drag to trim + Click somehwere in the Sequencer to insert a cut, click and drag to trim With this function you can quickly cut and remove a section of strips while keeping or collapsing the remaining gap. Press <kbd>Ctrl</kbd> to snap to cuts. @@ -186,10 +184,6 @@ class POWER_SEQUENCER_OT_mouse_trim(bpy.types.Operator): self.trim_apply(context, event) self.draw_stop() context.scene.use_audio_scrub = self.use_audio_scrub - - # FIXME: Workaround Blender 2.80's audio bug, remove when fixed in Blender - sequencer_workaround_2_80_audio_bug(context) - return {"FINISHED"} # Update trim @@ -217,8 +211,8 @@ class POWER_SEQUENCER_OT_mouse_trim(bpy.types.Operator): def draw_start(self, context, event): """Initializes the drawing handler, see draw()""" - to_select, to_delete = self.find_strips_to_trim(context) - target_strips = to_select + to_delete + to_trim, to_delete = self.find_strips_to_trim(context) + target_strips = to_trim + to_delete draw_args = (self, context, self.trim_start, self.trim_end, target_strips, self.gap_remove) self.draw_handler = bpy.types.SpaceSequenceEditor.draw_handler_add( @@ -263,12 +257,12 @@ class POWER_SEQUENCER_OT_mouse_trim(bpy.types.Operator): self.is_trimming = False def cut(self, context): - to_select = self.find_strips_to_cut(context) + to_cut = self.find_strips_to_cut(context) bpy.ops.sequencer.select_all(action="DESELECT") - for s in to_select: + for s in to_cut: s.select = True - if len(to_select) == 0: + if len(to_cut) == 0: bpy.ops.power_sequencer.gap_remove() else: frame_current = context.scene.frame_current @@ -298,10 +292,10 @@ class POWER_SEQUENCER_OT_mouse_trim(bpy.types.Operator): return to_cut def trim(self, context): - to_select, to_delete = self.find_strips_to_trim(context) - trim_strips(context, self.trim_start, self.trim_end, self.select_mode, to_select, to_delete) + to_trim, to_delete = self.find_strips_to_trim(context) + trim_strips(context, self.trim_start, self.trim_end, to_trim, to_delete) if (self.gap_remove and self.select_mode == "CURSOR") or ( - self.select_mode == "CONTEXT" and to_select == [] and to_delete == [] + self.select_mode == "CONTEXT" and to_trim == [] and to_delete == [] ): context.scene.frame_current = min(self.trim_start, self.trim_end) bpy.ops.power_sequencer.gap_remove() diff --git a/power_sequencer/operators/playback_speed_set.py b/power_sequencer/operators/playback_speed_set.py index 2e21f7b9..2dab7412 100644 --- a/power_sequencer/operators/playback_speed_set.py +++ b/power_sequencer/operators/playback_speed_set.py @@ -29,15 +29,15 @@ class POWER_SEQUENCER_OT_playback_speed_set(bpy.types.Operator): "demo": "", "description": doc_description(__doc__), "shortcuts": [ - ({"type": "NUMPAD_1", "ctrl": True, "value": "PRESS"}, {"speed": "NORMAL"}, "Speed to 1x"), - ({"type": "NUMPAD_2", "ctrl": True, "value": "PRESS"}, {"speed": "FAST"}, "Speed to 1.33x"), + ({"type": "ONE", "ctrl": True, "value": "PRESS"}, {"speed": "NORMAL"}, "Speed to 1x"), + ({"type": "TWO", "ctrl": True, "value": "PRESS"}, {"speed": "FAST"}, "Speed to 1.33x"), ( - {"type": "NUMPAD_3", "ctrl": True, "value": "PRESS"}, + {"type": "THREE", "ctrl": True, "value": "PRESS"}, {"speed": "FASTER"}, "Speed to 1.66x", ), - ({"type": "NUMPAD_4", "ctrl": True, "value": "PRESS"}, {"speed": "DOUBLE"}, "Speed to 2x"), - ({"type": "NUMPAD_5", "ctrl": True, "value": "PRESS"}, {"speed": "TRIPLE"}, "Speed to 3x"), + ({"type": "FOUR", "ctrl": True, "value": "PRESS"}, {"speed": "DOUBLE"}, "Speed to 2x"), + ({"type": "FIVE", "ctrl": True, "value": "PRESS"}, {"speed": "TRIPLE"}, "Speed to 3x"), ], "keymap": "Sequencer", } diff --git a/power_sequencer/operators/preview_to_selection.py b/power_sequencer/operators/preview_to_selection.py index 20d168dd..f084e767 100644 --- a/power_sequencer/operators/preview_to_selection.py +++ b/power_sequencer/operators/preview_to_selection.py @@ -49,8 +49,11 @@ class POWER_SEQUENCER_OT_preview_to_selection(bpy.types.Operator): return context.sequences def execute(self, context): - scene = context.scene - sequences = context.selected_sequences if len(context.selected_sequences) >= 1 else context.sequences + sequences = ( + context.selected_sequences + if len(context.selected_sequences) >= 1 + else context.sequences + ) frame_start, frame_end = get_frame_range(context, sequences) set_preview_range(context, frame_start, frame_end - 1) return {"FINISHED"} diff --git a/power_sequencer/operators/render_presets/youtube_1080.py b/power_sequencer/operators/render_presets/youtube_1080.py index 1b7940e2..49d0a651 100644 --- a/power_sequencer/operators/render_presets/youtube_1080.py +++ b/power_sequencer/operators/render_presets/youtube_1080.py @@ -31,12 +31,11 @@ if __name__ == "__main__": render.ffmpeg.constant_rate_factor = "PERC_LOSSLESS" render.ffmpeg.ffmpeg_preset = "BEST" - is_ntsc = render.fps != 25 - if is_ntsc: - render.ffmpeg.gopsize = 18 - else: - render.ffmpeg.gopsize = 15 - render.ffmpeg.use_max_b_frames = False + scene = bpy.context.scene + fps = scene.render.fps / scene.render.fps_base + render.ffmpeg.gopsize = round(fps / 2.0) + render.ffmpeg.use_max_b_frames = True + render.ffmpeg.max_b_frames = 2 render.ffmpeg.video_bitrate = 9000 render.ffmpeg.maxrate = 9000 @@ -44,3 +43,6 @@ if __name__ == "__main__": render.ffmpeg.buffersize = 224 * 8 render.ffmpeg.packetsize = 2048 render.ffmpeg.muxrate = 10080000 + + render.ffmpeg.audio_codec = "AAC" + render.ffmpeg.audio_bitrate = 384 diff --git a/power_sequencer/operators/ripple_delete.py b/power_sequencer/operators/ripple_delete.py index 6b88a8e9..a469148d 100644 --- a/power_sequencer/operators/ripple_delete.py +++ b/power_sequencer/operators/ripple_delete.py @@ -15,13 +15,14 @@ # not, see <https://www.gnu.org/licenses/>. # import bpy -from operator import attrgetter from .utils.doc import doc_brief, doc_description, doc_idname, doc_name -from .utils.functions import get_frame_range -from .utils.functions import get_mouse_frame_and_channel -from .utils.global_settings import SequenceTypes -from .utils.functions import slice_selection +from .utils.functions import ( + get_frame_range, + get_mouse_frame_and_channel, + slice_selection, + ripple_move, +) class POWER_SEQUENCER_OT_ripple_delete(bpy.types.Operator): @@ -58,6 +59,7 @@ class POWER_SEQUENCER_OT_ripple_delete(bpy.types.Operator): scene = context.scene sequencer = bpy.ops.sequencer selection = context.selected_sequences + selection_length = len(selection) audio_scrub_active = context.scene.use_audio_scrub context.scene.use_audio_scrub = False @@ -67,44 +69,28 @@ class POWER_SEQUENCER_OT_ripple_delete(bpy.types.Operator): is_single_channel = len(channels) == 1 if is_single_channel: - first_strip = selection_blocks[0][0] for block in selection_blocks: - sequencer.select_all(action="DESELECT") - block_strip_start = block[0] - delete_start = block_strip_start.frame_final_start + delete_start = block[0].frame_final_start delete_end = block[-1].frame_final_end - ripple_length = delete_end - delete_start - assert ripple_length > 0 - - for s in block: - s.select = True - sequencer.delete() - - strips_in_channel = [ - s - for s in bpy.context.sequences - if s.channel == channels[0] - and s.frame_final_start >= first_strip.frame_final_start - ] - strips_in_channel = sorted(strips_in_channel, key=attrgetter("frame_final_start")) - to_ripple = [s for s in strips_in_channel if s.frame_final_start > delete_start] - for s in to_ripple: - s.frame_start -= ripple_length + ripple_duration = abs(delete_end - delete_start) + ripple_move(context, block, -ripple_duration, delete=True) else: + cursor_frame = scene.frame_current for block in selection_blocks: sequencer.select_all(action="DESELECT") for s in block: s.select = True - selection_start, _ = get_frame_range(context, block) + selection_start = get_frame_range(context, block)[0] sequencer.delete() scene.frame_current = selection_start bpy.ops.power_sequencer.gap_remove() + scene.frame_current = cursor_frame self.report( {"INFO"}, - "Deleted " + str(len(selection)) + " sequence" + "s" if len(selection) > 1 else "", + "Deleted " + str(selection_length) + " sequence" + "s" if selection_length > 1 else "", ) context.scene.use_audio_scrub = audio_scrub_active diff --git a/power_sequencer/operators/scene_merge_from.py b/power_sequencer/operators/scene_merge_from.py index cab3e353..792799e8 100644 --- a/power_sequencer/operators/scene_merge_from.py +++ b/power_sequencer/operators/scene_merge_from.py @@ -59,7 +59,7 @@ class POWER_SEQUENCER_OT_merge_from_scene_strip(bpy.types.Operator): def execute(self, context): strip = context.scene.sequence_editor.active_strip if strip.type != "SCENE": - return {'FINISHED'} + return {"FINISHED"} strip_scene = strip.scene start_scene = context.window.scene diff --git a/power_sequencer/operators/scene_open_from_strip.py b/power_sequencer/operators/scene_open_from_strip.py index 9534855f..8bcbef77 100644 --- a/power_sequencer/operators/scene_open_from_strip.py +++ b/power_sequencer/operators/scene_open_from_strip.py @@ -46,7 +46,7 @@ class POWER_SEQUENCER_OT_open_scene_strip(bpy.types.Operator): def execute(self, context): active_strip = context.scene.sequence_editor.active_strip if active_strip.type != "SCENE": - return {'FINISHED'} + return {"FINISHED"} strip_scene = active_strip.scene context.screen.scene = bpy.data.scenes[strip_scene.name] diff --git a/power_sequencer/operators/scene_rename_with_strip.py b/power_sequencer/operators/scene_rename_with_strip.py index f142f8bd..245909b5 100644 --- a/power_sequencer/operators/scene_rename_with_strip.py +++ b/power_sequencer/operators/scene_rename_with_strip.py @@ -15,7 +15,6 @@ # not, see <https://www.gnu.org/licenses/>. # import bpy -import operator from .utils.doc import doc_name, doc_idname, doc_brief, doc_description diff --git a/power_sequencer/operators/snap_selection.py b/power_sequencer/operators/snap_selection.py index 0c93b76e..db661d06 100644 --- a/power_sequencer/operators/snap_selection.py +++ b/power_sequencer/operators/snap_selection.py @@ -15,7 +15,7 @@ # not, see <https://www.gnu.org/licenses/>. # import bpy -from .utils.functions import get_sequences_under_cursor +from .utils.functions import get_sequences_under_cursor, apply_time_offset from .utils.doc import doc_name, doc_idname, doc_brief, doc_description @@ -48,11 +48,10 @@ class POWER_SEQUENCER_OT_snap_selection(bpy.types.Operator): def execute(self, context): sequences = ( context.selected_sequences - if len(context.selected_sequences) > 0 + if context.selected_sequences else get_sequences_under_cursor(context) ) - time_move = context.scene.frame_current - sequences[0].frame_final_start - # bpy.ops.power_sequencer.select_related_strips() - for s in sequences: - s.frame_start += time_move + frame_first = min(sequences, key=lambda s: s.frame_final_start).frame_final_start + time_offset = context.scene.frame_current - frame_first + apply_time_offset(context, sequences, time_offset) return {"FINISHED"} diff --git a/power_sequencer/operators/space_sequences.py b/power_sequencer/operators/space_sequences.py index 3f7bb3b0..40985afb 100644 --- a/power_sequencer/operators/space_sequences.py +++ b/power_sequencer/operators/space_sequences.py @@ -16,7 +16,7 @@ # import bpy -from .utils.functions import get_mouse_frame_and_channel, convert_duration_to_frames +from .utils.functions import convert_duration_to_frames from .utils.global_settings import SequenceTypes from .utils.doc import doc_name, doc_idname, doc_brief, doc_description diff --git a/power_sequencer/operators/speed_up_movie_strip.py b/power_sequencer/operators/speed_up_movie_strip.py index 0c87b78b..27b66130 100644 --- a/power_sequencer/operators/speed_up_movie_strip.py +++ b/power_sequencer/operators/speed_up_movie_strip.py @@ -19,7 +19,6 @@ from math import ceil from .utils.global_settings import SequenceTypes from .utils.functions import slice_selection -from .utils.functions import find_linked from .utils.doc import doc_name, doc_idname, doc_brief, doc_description @@ -36,21 +35,9 @@ class POWER_SEQUENCER_OT_speed_up_movie_strip(bpy.types.Operator): "demo": "https://i.imgur.com/ZyEd0jD.gif", "description": doc_description(__doc__), "shortcuts": [ - ( - {"type": "NUMPAD_2", "value": "PRESS", "alt": True}, - {"speed_factor": 2.0}, - "Speed x2", - ), - ( - {"type": "NUMPAD_3", "value": "PRESS", "alt": True}, - {"speed_factor": 3.0}, - "Speed x3", - ), - ( - {"type": "NUMPAD_4", "value": "PRESS", "alt": True}, - {"speed_factor": 4.0}, - "Speed x4", - ), + ({"type": "TWO", "value": "PRESS", "alt": True}, {"speed_factor": 2.0}, "Speed x2",), + ({"type": "THREE", "value": "PRESS", "alt": True}, {"speed_factor": 3.0}, "Speed x3",), + ({"type": "FOUR", "value": "PRESS", "alt": True}, {"speed_factor": 4.0}, "Speed x4",), ], "keymap": "Sequencer", } @@ -101,16 +88,15 @@ class POWER_SEQUENCER_OT_speed_up_movie_strip(bpy.types.Operator): return sequence_editor = context.scene.sequence_editor - bpy.ops.sequencer.select_all(action="DESELECT") + sequencer = bpy.ops.sequencer + + sequencer.select_all(action="DESELECT") for s in sequences: s.select = True - bpy.ops.sequencer.meta_make() + sequencer.meta_make() meta_strip = sequence_editor.active_strip - if len(meta_strip.sequences) == 1: - meta_strip.sequences[0].frame_offset_start = 0 - meta_strip.sequences[0].frame_offset_end = 0 - bpy.ops.sequencer.effect_strip_add(type="SPEED") + sequencer.effect_strip_add(type="SPEED") speed_effect = sequence_editor.active_strip speed_effect.use_default_fade = False speed_effect.speed_factor = self.speed_factor @@ -121,7 +107,7 @@ class POWER_SEQUENCER_OT_speed_up_movie_strip(bpy.types.Operator): sequence_editor.active_strip = meta_strip speed_effect.select = True meta_strip.select = True - bpy.ops.sequencer.meta_make() + sequencer.meta_make() sequence_editor.active_strip.name = ( meta_strip.sequences[0].name + " " + str(self.speed_factor) + "x" ) diff --git a/power_sequencer/operators/synchronize_titles.py b/power_sequencer/operators/synchronize_titles.py deleted file mode 100644 index 3df342f4..00000000 --- a/power_sequencer/operators/synchronize_titles.py +++ /dev/null @@ -1,112 +0,0 @@ -# -# Copyright (C) 2016-2019 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors -# -# This file is part of Power Sequencer. -# -# Power Sequencer is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# Power Sequencer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with Power Sequencer. If -# not, see <https://www.gnu.org/licenses/>. -# -import bpy - -from .utils.doc import doc_name, doc_idname, doc_brief, doc_description - - -# TODO: rewrite to sync strips to corresponding identifiers instead -# See https://github.com/GDquest/Blender-power-sequencer/issues/55 -class POWER_SEQUENCER_OT_synchronize_titles(bpy.types.Operator): - """ - *brief* Snap the selected image or text strips to the corresponding title marker - - - The marker and strip names have to start with TITLE-001 - """ - - doc = { - "name": doc_name(__qualname__), - "demo": "", - "description": doc_description(__doc__), - "shortcuts": [], - "keymap": "Sequencer", - } - bl_idname = doc_idname(__qualname__) - bl_label = doc["name"] - bl_description = doc_brief(doc["description"]) - bl_options = {"REGISTER", "UNDO"} - - TITLE_REGEX = r"^TITLE-?([0-9]+)-?" - - @classmethod - def poll(cls, context): - return context.scene.sequence_editor - - def execute(self, context): - markers = context.scene.timeline_markers - selection = context.selected_sequences - - if not markers and selection: - if not markers: - self.report({"INFO"}, "No markers, operation cancelled.") - else: - self.report({"INFO"}, "No sequences selected, operation cancelled.") - return {"CANCELLED"} - - title_markers = self.find_markers(context, self.TITLE_REGEX) - if not title_markers: - self.report({"INFO"}, "No title markers found, operation cancelled.") - - matched = self.match_sequences_and_markers(selection, title_markers, self.TITLE_REGEX) - for s, m in matched: - s.frame_start = m.frame - return {"FINISHED"} - - def match_sequences_and_markers(self, sequences, markers, regex): - """Takes a list of sequences, of markers, and checks if they both - match a regular expression. - Returns a list of pairs of sequence and marker as tuples - - Args: - - sequences, the list of sequences - - markers, a list of markers - - regex, the regular expression to match""" - if not sequences and markers and regex: - raise AttributeError("missing attributes") - - import re - from .utils.global_settings import SequenceTypes - - sequences = (s for s in sequences if s.type not in SequenceTypes.EFFECT) - - return_list = [] - re_title = re.compile(regex) - for s in sequences: - found = re_title.match(s.name) - title_id = int(found.group(1)) if found else None - for m in markers: - found = re_title.match(m.name) - marker_id = int(found.group(1)) if found else None - if marker_id == title_id: - return_list.append((s, m)) - break - return return_list - - def find_markers(self, context, regex): - """Finds and returns all markers using REGEX - Args: - - regex, the re match pattern to use""" - if not regex: - raise AttributeError("regex parameter missing") - - import re - - regex = re.compile(regex) - markers = context.scene.timeline_markers - markers = (m for m in markers if regex.match(m.name)) - return markers diff --git a/power_sequencer/operators/transitions_remove.py b/power_sequencer/operators/transitions_remove.py index aabb2af5..4ebd2b44 100644 --- a/power_sequencer/operators/transitions_remove.py +++ b/power_sequencer/operators/transitions_remove.py @@ -44,7 +44,6 @@ class POWER_SEQUENCER_OT_transitions_remove(bpy.types.Operator): def poll(cls, context): return context.selected_sequences - def execute(self, context): to_process = ( self.sequences_override if self.sequences_override else context.selected_sequences diff --git a/power_sequencer/operators/trim_to_surrounding_cuts.py b/power_sequencer/operators/trim_to_surrounding_cuts.py index 11ece536..0d4c012b 100644 --- a/power_sequencer/operators/trim_to_surrounding_cuts.py +++ b/power_sequencer/operators/trim_to_surrounding_cuts.py @@ -19,14 +19,10 @@ Find the two closest cuts, trims and deletes all strips above in the range but l margin. Removes the newly formed gap. """ import bpy -from math import floor -from .utils.functions import convert_duration_to_frames +from .utils.functions import convert_duration_to_frames, trim_strips from .utils.doc import doc_name, doc_idname, doc_brief, doc_description -from .utils.functions import ( - find_closest_surrounding_cuts_frames, - sequencer_workaround_2_80_audio_bug, -) +from .utils.functions import find_closest_surrounding_cuts_frames class POWER_SEQUENCER_OT_trim_to_surrounding_cuts(bpy.types.Operator): @@ -38,6 +34,7 @@ class POWER_SEQUENCER_OT_trim_to_surrounding_cuts(bpy.types.Operator): By default, the tool leaves a 0.2 seconds margin on either side of the trim. """ + doc = { "name": doc_name(__qualname__), "demo": "", @@ -76,14 +73,13 @@ class POWER_SEQUENCER_OT_trim_to_surrounding_cuts(bpy.types.Operator): if not context.sequences: return {"CANCELLED"} - sequencer = bpy.ops.sequencer - - frame = context.region.view2d.region_to_view(x=event.mouse_region_x, y=event.mouse_region_y)[0] + frame = context.region.view2d.region_to_view( + x=event.mouse_region_x, y=event.mouse_region_y + )[0] frame = round(frame) - left_cut_frame, right_cut_frame = find_closest_surrounding_cuts_frames(context, frame) margin_frame = convert_duration_to_frames(context, self.margin) - + left_cut_frame, right_cut_frame = find_closest_surrounding_cuts_frames(context, frame) if abs(left_cut_frame - right_cut_frame) <= margin_frame * 2: self.report( {"WARNING"}, @@ -94,26 +90,7 @@ class POWER_SEQUENCER_OT_trim_to_surrounding_cuts(bpy.types.Operator): to_delete, to_trim = self.find_strips_in_range(context, left_cut_frame, right_cut_frame) trim_start, trim_end = (left_cut_frame + margin_frame, right_cut_frame - margin_frame) - for s in to_trim: - # If the strip is larger than the range to trim cut it in three - if s.frame_final_start < trim_start and s.frame_final_end > trim_end: - sequencer.select_all(action="DESELECT") - s.select = True - sequencer.cut(frame=trim_start, type="SOFT", side="RIGHT") - sequencer.cut(frame=trim_end, type="SOFT", side="LEFT") - to_delete.append(context.selected_sequences[0]) - continue - - if s.frame_final_start < trim_end and s.frame_final_end > trim_end: - s.frame_final_start = trim_end - elif s.frame_final_end > trim_start and s.frame_final_start < trim_start: - s.frame_final_end = trim_start - - # Delete all sequences that are between the cuts - sequencer.select_all(action="DESELECT") - for s in to_delete: - s.select = True - sequencer.delete() + trim_strips(context, trim_start, trim_end, to_trim, to_delete) if self.gap_remove: frame_to_remove_gap = right_cut_frame - 1 if frame == right_cut_frame else frame @@ -122,7 +99,6 @@ class POWER_SEQUENCER_OT_trim_to_surrounding_cuts(bpy.types.Operator): bpy.ops.power_sequencer.gap_remove() context.scene.frame_current = trim_start - sequencer_workaround_2_80_audio_bug(context) return {"FINISHED"} def find_strips_in_range( diff --git a/power_sequencer/operators/utils/doc.py b/power_sequencer/operators/utils/doc.py index 82a87581..0a3e0a83 100644 --- a/power_sequencer/operators/utils/doc.py +++ b/power_sequencer/operators/utils/doc.py @@ -18,7 +18,6 @@ Utilities to convert operator names and docstrings to human-readable text. Used to generate names for Blender's operator search, and to generate Power Sequencer's documentation. """ -import re upper_match = lambda m: m.string diff --git a/power_sequencer/operators/utils/functions.py b/power_sequencer/operators/utils/functions.py index 35fd900d..e2ea3672 100644 --- a/power_sequencer/operators/utils/functions.py +++ b/power_sequencer/operators/utils/functions.py @@ -14,10 +14,11 @@ # You should have received a copy of the GNU General Public License along with Power Sequencer. If # not, see <https://www.gnu.org/licenses/>. # -import bpy -import subprocess -from math import sqrt, floor +from math import floor, sqrt from operator import attrgetter + +import bpy + from .global_settings import SequenceTypes @@ -258,38 +259,50 @@ def slice_selection(context, sequences): return broken_selection -def trim_strips( - context, start_frame, end_frame, select_mode, strips_to_trim=[], strips_to_delete=[] -): +def trim_strips(context, start_frame, end_frame, to_trim=[], to_delete=[]): """ Remove the footage and audio between start_frame and end_frame. """ trim_start = min(start_frame, end_frame) trim_end = max(start_frame, end_frame) - strips_to_trim = [s for s in strips_to_trim if s.type in SequenceTypes.CUTABLE] + to_trim = [s for s in to_trim if s.type in SequenceTypes.CUTABLE] - for s in strips_to_trim: - if s.frame_final_start < trim_start and s.frame_final_end > trim_end: + for s in to_trim: + # Cut strip longer than the trim range in three + is_strip_longer_than_trim_range = ( + s.frame_final_start < trim_start and s.frame_final_end > trim_end + ) + if is_strip_longer_than_trim_range: bpy.ops.sequencer.select_all(action="DESELECT") s.select = True bpy.ops.sequencer.cut(frame=trim_start, type="SOFT", side="RIGHT") bpy.ops.sequencer.cut(frame=trim_end, type="SOFT", side="LEFT") - strips_to_delete.append(context.selected_sequences[0]) + to_delete.append(context.selected_sequences[0]) continue + + # Resize strips that overlap the trim range elif s.frame_final_start < trim_end and s.frame_final_end > trim_end: s.frame_final_start = trim_end elif s.frame_final_end > trim_start and s.frame_final_start < trim_start: s.frame_final_end = trim_start - if strips_to_delete != []: - bpy.ops.sequencer.select_all(action="DESELECT") - for s in strips_to_delete: - s.select = True - bpy.ops.sequencer.delete() + delete_strips(to_delete) return {"FINISHED"} +def delete_strips(to_delete=[]): + """ + Remove the footage and audio between start_frame and end_frame. + """ + if to_delete == []: + return + bpy.ops.sequencer.select_all(action="DESELECT") + for s in to_delete: + s.select = True + bpy.ops.sequencer.delete() + + def find_closest_surrounding_cuts(context, frame): """ Returns a tuple of (strip_before, strip_after), the two closest sequences around a gap. @@ -333,11 +346,48 @@ def get_sequences_under_cursor(context): return under_cursor -def sequencer_workaround_2_80_audio_bug(context): - for s in context.sequences: - if s.lock: - continue +def ripple_move(context, sequences, duration_frames, delete=False): + """Moves sequences in the list and ripples the change to all sequences after them, in the corresponding channels + The `duration_frames` can be positive or negative. + If `delete` is True, deletes every sequence in `sequences`. + """ + channels = {s.channel for s in sequences} + first_strip = min(sequences, key=lambda s: s.frame_final_start) + to_ripple = [ + s + for s in context.sequences + if s.channel in channels and s.frame_final_start >= first_strip.frame_final_start + ] + + if delete: + bpy.ops.sequencer.select_all(action="DESELECT") + for s in sequences: + s.select = True + bpy.ops.sequencer.delete() + else: + to_ripple = set(to_ripple + sequences) + + # Use the built-in seq_slide operator to move strips, for best performances + initial_selection = context.selected_sequences + bpy.ops.sequencer.select_all(action="DESELECT") + for s in to_ripple: + s.select = True + bpy.ops.transform.seq_slide(value=(duration_frames, 0)) + bpy.ops.sequencer.select_all(action="DESELECT") + for s in initial_selection: + s.select = True + + +def apply_time_offset(context, sequences=[], offset=0): + """Offsets a list of sequences in time using bpy.ops.transform.seq_slide. Mutates and restores the + user's selection. Use this function to ensure maximum performances and avoid having to figure + out the logic to move strips in the right order. + """ + selection = context.selected_sequences + bpy.ops.sequencer.select_all(action="DESELECT") + for s in sequences: + s.select = True + bpy.ops.transform.seq_slide(value=(offset, 0)) + bpy.ops.sequencer.select_all(action="DESELECT") + for s in selection: s.select = True - bpy.ops.transform.seq_slide(value=(0, 0)) - s.select = False - break diff --git a/power_sequencer/operators/utils/global_settings.py b/power_sequencer/operators/utils/global_settings.py index 88590783..03d94365 100644 --- a/power_sequencer/operators/utils/global_settings.py +++ b/power_sequencer/operators/utils/global_settings.py @@ -58,7 +58,7 @@ class SequenceTypes: SOUND = ("SOUND",) IMAGE = ("IMAGE",) TRANSITIONABLE = ( - VIDEO + IMAGE + ("MULTICAM", "GAUSSIAN_BLUR", "TRANSFORM", "ADJUSTMENT", "SPEED") + VIDEO + IMAGE + ("MULTICAM", "GAUSSIAN_BLUR", "TRANSFORM", "ADJUSTMENT", "SPEED", "COLOR") ) # Strips that can be cut. If most effect strips are linked to their inputs # and shouldn't be cut, some can be edited directly diff --git a/power_sequencer/operators/utils/info_progress_bar.py b/power_sequencer/operators/utils/info_progress_bar.py index a3049bf5..fc23c3c1 100644 --- a/power_sequencer/operators/utils/info_progress_bar.py +++ b/power_sequencer/operators/utils/info_progress_bar.py @@ -15,7 +15,6 @@ # not, see <https://www.gnu.org/licenses/>. # import bpy -import time class InfoProgressBar: |