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 | |
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
49 files changed, 433 insertions, 596 deletions
diff --git a/power_sequencer/__init__.py b/power_sequencer/__init__.py index a51f7342..670bcf41 100644..100755 --- a/power_sequencer/__init__.py +++ b/power_sequencer/__init__.py @@ -14,21 +14,23 @@ # 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 typing import List, Tuple, Type + import bpy from .addon_preferences import register_preferences, unregister_preferences from .addon_properties import register_properties, unregister_properties -from .operators import classes -from .utils.register_shortcuts import register_shortcuts from .handlers import register_handlers, unregister_handlers -from .utils import addon_auto_imports +from .operators import get_operator_classes +from .tools import get_tool_classes from .ui import register_ui, unregister_ui - +from .utils import addon_auto_imports +from .utils.register_shortcuts import register_shortcuts # load and reload submodules ################################## modules = addon_auto_imports.setup_addon_modules( - __path__, __name__, ignore_packages=[".utils", ".audiosync"], ignore_modules=[] + __path__, __name__, ignore_packages=[".utils", ".audiosync"] ) @@ -38,43 +40,68 @@ bl_info = { "author": "Nathan Lovato", "version": (1, 4, 0), "blender": (2, 80, 0), - "location": "Video Tools", + "location": "Sequencer", "tracker_url": "https://github.com/GDquest/Blender-power-sequencer/issues", - "wiki_url": "https://www.youtube.com/playlist?list=PLhqJJNjsQ7KFjp88Cu57Zb9_wFt7nlkEI", + "wiki_url": "https://www.gdquest.com/docs/documentation/power-sequencer/", "support": "COMMUNITY", "category": "Sequencer", } -addon_keymaps = [] +# We use globals to cache loaded keymaps, operators, and tools +addon_keymaps: List[Type] = [] +classes_operator: List[Type] = [] +classes_tool: List[Type] = [] def register(): global addon_keymaps + global classes_operator + global classes_tool register_preferences() register_properties() register_handlers() register_ui() - for c in classes: - bpy.utils.register_class(c) - - keymaps = register_shortcuts() + # Register operators + classes_operator = get_operator_classes() + for cls in classes_operator: + bpy.utils.register_class(cls) + + # Register tools + version_min_toolbar = (2, 83, 0) + if is_blender_version_compatible(version_min_toolbar): + classes_tool = get_tool_classes() + last_tool = {"builtin.cut"} + for index, cls in enumerate(classes_tool): + bpy.utils.register_tool(cls, after=last_tool, separator=index == 0) + last_tool = {cls.bl_idname} + + # Register keymaps + keymaps = register_shortcuts(classes_operator) addon_keymaps += keymaps print("Registered {} with {} modules".format(bl_info["name"], len(modules))) def unregister(): + """Unregister""" global addon_keymaps + global classes_operator + global classes_tool for km, kmi in addon_keymaps: km.keymap_items.remove(kmi) addon_keymaps.clear() - for c in classes: - bpy.utils.unregister_class(c) + for cls in classes_operator: + bpy.utils.unregister_class(cls) + + version_min_toolbar = (2, 82, 0) + if is_blender_version_compatible(version_min_toolbar): + for cls in classes_tool: + bpy.utils.unregister_tool(cls) unregister_ui() unregister_preferences() @@ -82,3 +109,11 @@ def unregister(): unregister_handlers() print("Unregistered {}".format(bl_info["name"])) + + +def is_blender_version_compatible(version: Tuple[int, int, int]) -> bool: + """Returns True if the `version` is greater or equal to the current Blender version. + Converts the versions to integers to compare them.""" + version_int = version[0] * 1000 + version[1] * 10 + version[2] + blender_version_int = bpy.app.version[0] * 1000 + bpy.app.version[1] * 10 + bpy.app.version[2] + return blender_version_int >= version_int diff --git a/power_sequencer/addon_preferences.py b/power_sequencer/addon_preferences.py index 0e8e7d71..fcc67e9c 100644 --- a/power_sequencer/addon_preferences.py +++ b/power_sequencer/addon_preferences.py @@ -17,12 +17,16 @@ """ Add-on preferences and interface in the Blender preferences window. """ +import subprocess + import bpy +from bpy.props import BoolProperty, StringProperty def get_preferences(context): return context.preferences.addons[__package__].preferences + class PowerSequencerPreferences(bpy.types.AddonPreferences): bl_idname = __package__ @@ -31,6 +35,32 @@ class PowerSequencerPreferences(bpy.types.AddonPreferences): proxy_75: bpy.props.BoolProperty(name="75%", default=False) proxy_100: bpy.props.BoolProperty(name="100%", default=False) + # Code adapted from Krzysztof Trzciński's work + ffmpeg_executable: StringProperty( + name="Path to ffmpeg executable", + default="", + update=lambda self, context: self.update_ffmpeg_executable(context), + subtype="FILE_PATH", + ) + ffmpeg_status: StringProperty(default="") + ffmpeg_is_executable_valid: BoolProperty(default=False) + + def update_ffmpeg_executable(self, context): + error_message, info = self._try_run_ffmpeg(self.ffmpeg_executable) + self.ffmpeg_is_executable_valid = error_message == "" + self.ffmpeg_status = error_message if error_message != "" else info + + def _try_run_ffmpeg(self, path): + """Runs ffmpeg -version, and returns an error message if it failed""" + error_message, info = "", "" + try: + info: str = subprocess.check_output([path, "-version"]).decode("utf-8") + info = info[:info.find("Copyright")] + print(info) + except (OSError, subprocess.CalledProcessError): + error_message = "Path `{}` is not a valid ffmpeg executable".format(path) + return error_message, info + def draw(self, context): layout = self.layout @@ -42,6 +72,16 @@ class PowerSequencerPreferences(bpy.types.AddonPreferences): row.prop(self, "proxy_75") row.prop(self, "proxy_100") + text = [ + "(Optional) FFMpeg executable to use for multithread renders and proxy generation. " + "Use this to render with a version of ffmpeg that's not on your system's PATH variable." + ] + for line in text: + layout.label(text=line) + layout.prop(self, "ffmpeg_executable") + icon = "INFO" if self.ffmpeg_is_executable_valid else "ERROR" + layout.label(text=self.ffmpeg_status, icon=icon) + register_preferences, unregister_preferences = bpy.utils.register_classes_factory( [PowerSequencerPreferences] 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: diff --git a/power_sequencer/scripts/BPSProxy/bpsproxy/commands.py b/power_sequencer/scripts/BPSProxy/bpsproxy/commands.py index a2f4388e..72c81ccf 100644 --- a/power_sequencer/scripts/BPSProxy/bpsproxy/commands.py +++ b/power_sequencer/scripts/BPSProxy/bpsproxy/commands.py @@ -70,7 +70,7 @@ def get_commands_image_1(cfg, clargs, **kwargs): out: iter(tuple(str)) Iterator containing commands. """ - cmd = "ffmpeg -y -v quiet -stats -i '{path_i_1}' {common_all}" + cmd = "ffmpeg -hwaccel auto -y -v quiet -stats -i '{path_i_1}' {common_all}" common = "-f apng -filter:v scale=iw*{size}:ih*{size} '{path_o_1}'" common_all = map(lambda s: kwargs["path_o_1"].format(size=s), clargs.sizes) common_all = map( @@ -101,7 +101,7 @@ def get_commands_video_1(cfg, clargs, **kwargs): out: iter(tuple(str)) Iterator containing commands. """ - cmd = "ffmpeg -y -v quiet -stats -i '{path_i_1}' {common_all}" + cmd = "ffmpeg -hwaccel auto -y -v quiet -stats -i '{path_i_1}' {common_all}" common = ( "-pix_fmt yuv420p" " -g 1" @@ -186,5 +186,5 @@ def get_commands_vi(cfg, clargs, **kwargs): An iterator with the 1st element as a tag (the `what` parameter) and the 2nd element as the iterator of the actual commands. """ - ws = filter(lambda x: x != "all", cfg["extensions"]) + ws = filter(lambda x: x is not "all", cfg["extensions"]) return chain.from_iterable(map(lambda w: get_commands(cfg, clargs, what=w, **kwargs), ws)) diff --git a/power_sequencer/scripts/BPSProxy/bpsproxy/config.py b/power_sequencer/scripts/BPSProxy/bpsproxy/config.py index eada47d5..28554e85 100644 --- a/power_sequencer/scripts/BPSProxy/bpsproxy/config.py +++ b/power_sequencer/scripts/BPSProxy/bpsproxy/config.py @@ -29,7 +29,7 @@ CONFIG = { }, "presets": { "webm": "-c:v libvpx -crf 25 -speed 16 -threads {}".format(str(mp.cpu_count())), - "mp4": "-c:v libx264 -crf 25 -preset faster", + "mp4": "-c:v libx264 -crf 25 -preset faster -tune fastdecode", "nvenc": "-c:v h264_nvenc -qp 25 -preset fast", }, "pre": {"work": "»", "done": "•", "skip": "~"}, diff --git a/power_sequencer/scripts/BPSProxy/bpsproxy/utils.py b/power_sequencer/scripts/BPSProxy/bpsproxy/utils.py index a33c34a9..832a0beb 100644 --- a/power_sequencer/scripts/BPSProxy/bpsproxy/utils.py +++ b/power_sequencer/scripts/BPSProxy/bpsproxy/utils.py @@ -36,14 +36,14 @@ def checktools(tools): msg = ["BPSProxy couldn't find external dependencies:"] msg += [ "[{check}] {tool}: {path}".format( - check="v" if path != "" else "X", tool=tool, path=path or "NOT FOUND" + check="v" if path is not "" else "X", tool=tool, path=path or "NOT FOUND" ) for tool, path in check["tools"] ] msg += [ ( "Check if you have them properly installed and available in the PATH" - " environment variable." + " environemnt variable." ) ] raise ToolError("\n".join(msg)) diff --git a/power_sequencer/scripts/BPSProxy/setup.py b/power_sequencer/scripts/BPSProxy/setup.py index f1dc3f59..996ba3db 100644 --- a/power_sequencer/scripts/BPSProxy/setup.py +++ b/power_sequencer/scripts/BPSProxy/setup.py @@ -14,22 +14,23 @@ # 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 setuptools import setup + def readme(): - with open("README.rst") as f: + with open("README.md") as f: return f.read() if __name__ == "__main__": - from setuptools import setup - setup( name="bpsproxy", - version="0.1.3.post1", + version="0.2.0", description="Blender Power Sequencer proxy generator tool", long_description=readme(), + long_description_content_type="text/markdown", classifiers=[ - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", @@ -43,7 +44,7 @@ if __name__ == "__main__": "Topic :: Multimedia :: Video", "Topic :: Utilities", ], - url="https://gitlab.com/razcore/bpsproxy", + url="https://github.com/GDquest/BPSProxy", keywords="blender proxy vse sequence editor productivity", author="Răzvan C. Rădulescu", author_email="razcore.art@gmail.com", diff --git a/power_sequencer/scripts/BPSRender/bpsrender/commands.py b/power_sequencer/scripts/BPSRender/bpsrender/commands.py index dc669806..910ff021 100644 --- a/power_sequencer/scripts/BPSRender/bpsrender/commands.py +++ b/power_sequencer/scripts/BPSRender/bpsrender/commands.py @@ -249,7 +249,7 @@ def get_commands_join(cfg, clargs, **kwargs): "-c:a", "aac", "-b:a", - "192k", + "320k", "-y", kwargs["render_audiovideo_path"], ) diff --git a/power_sequencer/scripts/BPSRender/bpsrender/helpers.py b/power_sequencer/scripts/BPSRender/bpsrender/helpers.py index df74d333..9ebcf2b0 100644 --- a/power_sequencer/scripts/BPSRender/bpsrender/helpers.py +++ b/power_sequencer/scripts/BPSRender/bpsrender/helpers.py @@ -41,14 +41,14 @@ def checktools(tools): msg = ["BPSRender couldn't find external dependencies:"] msg += [ "[{check}] {tool}: {path}".format( - check="v" if path != "" else "X", tool=tool, path=path or "NOT FOUND" + check="v" if path is not "" else "X", tool=tool, path=path or "NOT FOUND" ) for tool, path in check["tools"] ] msg += [ ( "Check if you have them properly installed and available in the PATH" - " environment variable." + " environemnt variable." ), "Exiting...", ] diff --git a/power_sequencer/scripts/BPSRender/bpsrender/setup.py b/power_sequencer/scripts/BPSRender/bpsrender/setup.py index 9aa044c2..aba30d07 100644 --- a/power_sequencer/scripts/BPSRender/bpsrender/setup.py +++ b/power_sequencer/scripts/BPSRender/bpsrender/setup.py @@ -139,7 +139,7 @@ def setup_folders_hdd(cfg, clargs, **kwargs): ------- out: (iter((str, iter(tuple))), dict) 1st element: see commands.py:get_commands_all - 2nd element: the keyword arguments used by calls.py:call + 2nd elment: the keyword arguments used by calls.py:call """ # create folder structure if it doesn't exist already only if # appropriate command line arguments are given @@ -170,7 +170,7 @@ def setup(cfg, clargs): ------- out: (iter((str, iter(tuple))), dict) 1st element: see commands.py:get_commands_all - 2nd element: the keyword arguments used by calls.py:call + 2nd elment: the keyword arguments used by calls.py:call """ setups_f = (setup_bspy, setup_probe, setup_paths, setup_folders_hdd) lg.basicConfig(level=LOGLEV[min(clargs.verbose, len(LOGLEV) - 1)]) diff --git a/power_sequencer/scripts/BPSRender/setup.py b/power_sequencer/scripts/BPSRender/setup.py index 3d829d64..1f17b9ba 100644 --- a/power_sequencer/scripts/BPSRender/setup.py +++ b/power_sequencer/scripts/BPSRender/setup.py @@ -14,6 +14,8 @@ # 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 setuptools import setup + def readme(): with open("README.rst") as f: @@ -21,8 +23,6 @@ def readme(): if __name__ == "__main__": - from setuptools import setup - setup( name="bpsrender", version="0.1.40.post1", diff --git a/power_sequencer/tools/__init__.py b/power_sequencer/tools/__init__.py new file mode 100644 index 00000000..4c0e75ca --- /dev/null +++ b/power_sequencer/tools/__init__.py @@ -0,0 +1,33 @@ +# +# Copyright (C) 2016-2020 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 importlib +import os + + +def get_tool_classes(): + """Returns the list of tools 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 = [] + for path in module_paths: + module = importlib.import_module(path, package="blender_power_sequencer.tools") + tool_names = [entry for entry in dir(module) if entry.startswith("POWER_SEQUENCER_TOOL")] + classes.extend([getattr(module, name) for name in tool_names]) + return classes diff --git a/power_sequencer/tools/trim.py b/power_sequencer/tools/trim.py new file mode 100644 index 00000000..e6a54437 --- /dev/null +++ b/power_sequencer/tools/trim.py @@ -0,0 +1,45 @@ +# +# Copyright (C) 2016-2020 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 bpy.types import WorkSpaceTool + + +class POWER_SEQUENCER_TOOL_Trim(WorkSpaceTool): + bl_space_type = "SEQUENCE_EDITOR" + bl_context_mode = "SEQUENCER" + + bl_idname = "power_sequencer.trim" + bl_label = "Trim" + bl_description = "Cut and trim strips with the mouse" + bl_icon = "ops.mesh.knife_tool" + bl_widget = None + bl_keymap = ( + ( + "power_sequencer.mouse_trim", + {"type": "LEFTMOUSE", "value": "PRESS"}, + {"select_mode": "CONTEXT", "gap_remove": False}, + ), + ( + "power_sequencer.mouse_trim", + {"type": "LEFTMOUSE", "value": "PRESS", "shift": True}, + {"select_mode": "CURSOR", "gap_remove": True}, + ), + ) + + def draw_settings(context, layout, tool): + props = tool.operator_properties("power_sequencer.mouse_trim") + layout.prop(props, "mode") diff --git a/power_sequencer/ui/menu_contextual.py b/power_sequencer/ui/menu_contextual.py index 6ba0640a..c145beab 100644 --- a/power_sequencer/ui/menu_contextual.py +++ b/power_sequencer/ui/menu_contextual.py @@ -69,9 +69,7 @@ class POWER_SEQUENCER_MT_contextual(bpy.types.Menu): layout.operator( "power_sequencer.ripple_delete", icon="AUTOMERGE_ON", text="Ripple delete" ) - layout.operator( - "power_sequencer.snap_selection", icon="SNAP_ON", text="Snap selection" - ) + layout.operator("power_sequencer.snap_selection", icon="SNAP_ON", text="Snap selection") layout.separator() diff --git a/power_sequencer/ui/menu_toolbar.py b/power_sequencer/ui/menu_toolbar.py index 744ed7b9..3510d157 100644..100755 --- a/power_sequencer/ui/menu_toolbar.py +++ b/power_sequencer/ui/menu_toolbar.py @@ -52,7 +52,7 @@ class POWER_SEQUENCER_MT_playback(bpy.types.Menu): class POWER_SEQUENCER_MT_strips(bpy.types.Menu): - bl_label = "Strips" + bl_label = "Strip" def draw(self, context): layout = self.layout @@ -72,7 +72,7 @@ class POWER_SEQUENCER_MT_strips(bpy.types.Menu): class POWER_SEQUENCER_MT_transitions(bpy.types.Menu): - bl_label = "Transitions" + bl_label = "Transition" def draw(self, context): layout = self.layout @@ -132,7 +132,7 @@ class POWER_SEQUENCER_MT_edit(bpy.types.Menu): class POWER_SEQUENCER_MT_markers(bpy.types.Menu): - bl_label = "Markers" + bl_label = "Marker" def draw(self, context): layout = self.layout @@ -142,7 +142,6 @@ class POWER_SEQUENCER_MT_markers(bpy.types.Menu): layout.separator() - layout.operator("power_sequencer.marker_go_to_next") layout.operator("power_sequencer.copy_markers_as_timecodes") layout.operator("power_sequencer.marker_snap_to_cursor") layout.operator("power_sequencer.set_preview_between_markers") diff --git a/power_sequencer/utils/register_shortcuts.py b/power_sequencer/utils/register_shortcuts.py index 074fb1c3..5ecfdb30 100644 --- a/power_sequencer/utils/register_shortcuts.py +++ b/power_sequencer/utils/register_shortcuts.py @@ -35,14 +35,11 @@ def set_keymap_property(properties, property_name, value): print("Warning: %r" % e) -def register_shortcuts(): +def register_shortcuts(operator_classes): def keymapgetter(operator): return operator[1]["keymap"] - data = dir(operators) - data = filter(lambda operator: operator[0].isupper(), data) - data = map(lambda operator: op.attrgetter(operator), data) - data = map(lambda operator: operator(operators), data) + data = operator_classes data = map(lambda operator: op.attrgetter("bl_idname", "doc")(operator), data) data = {k: v for k, v in data if v != {}} data.update(operators.doc) |