Large diffs are not rendered by default.

@@ -1,6 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.convert_duration_to_frames import convert_duration_to_frames
from .utils.functions import convert_duration_to_frames
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


@@ -56,7 +58,7 @@ class POWER_SEQUENCER_OT_jump_time_offset(bpy.types.Operator):

@classmethod
def poll(cls, context):
return context is not None
return context.scene

def execute(self, context):
direction = 1 if self.direction == "forward" else -1
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
from operator import attrgetter

@@ -40,64 +42,71 @@ class POWER_SEQUENCER_OT_jump_to_cut(bpy.types.Operator):
name="Direction",
description="Jump direction, either forward or backward",
items=[
("RIGHT", "Forward", "Jump forward in time"),
("LEFT", "Backward", "Jump backward in time"),
("RIGHT", "Right", "Jump forward in time"),
("LEFT", "Left", "Jump backward in time"),
],
)

@classmethod
def poll(cls, context):
return context.sequences and len(context.sequences) > 0
return context.sequences

def execute(self, context):
frame_current = context.scene.frame_current
sorted_sequences = sorted(
context.sequences, key=attrgetter("frame_final_start", "frame_final_end")
)

fcurves = []
animation_data = context.scene.animation_data
if animation_data and animation_data.action:
fcurves = animation_data.action.fcurves

frame_target = -1

# First find the closest cut, then if that sequence has an associated
# fcurve, loop through the curve's keyframes.
if self.direction == "RIGHT":
sequences = [s for s in sorted_sequences if s.frame_final_end > frame_current]
for s in sequences:
frame_target = 100_000_000
for s in context.sequences:
if s.frame_final_end <= frame_current:
continue

candidates = [frame_target, s.frame_final_end]
if s.frame_final_start > frame_current:
candidates.append(s.frame_final_start)

frame_target = (
s.frame_final_end
if s.frame_final_start <= frame_current
else s.frame_final_start
)
frame_target = min(candidates)

for f in fcurves:
if s.name not in f.data_path:
continue

for k in f.keyframe_points:
frame = k.co[0]
if frame <= context.scene.frame_current:
if frame <= frame_current:
continue
frame_target = min(frame_target, frame)
break

elif self.direction == "LEFT":
sequences = [
s for s in reversed(sorted_sequences) if s.frame_final_start < frame_current
]
for s in sequences:
for s in context.sequences:
if s.frame_final_start >= frame_current:
continue

frame_target = (
s.frame_final_start if s.frame_final_end >= frame_current else s.frame_final_end
)
candidates = [frame_target, s.frame_final_start]
if s.frame_final_end < frame_current:
candidates.append(s.frame_final_end)

frame_target = max(candidates)

for f in fcurves:
if s.name not in f.data_path:
continue

for k in f.keyframe_points:
frame = k.co[0]
if frame >= context.scene.frame_current:
if frame >= frame_current:
continue
frame_target = max(frame_target, frame)
break

if frame_target != -1:
context.scene.frame_current = max(1, frame_target)
if frame_target > 0 and frame_target != 100_000_000:
context.scene.frame_current = int(frame_target)

return {"FINISHED"}
@@ -1,17 +1,18 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
import operator

from .utils.global_settings import SequenceTypes
from .utils.convert_duration_to_frames import convert_duration_to_frames
from .utils.functions import convert_duration_to_frames
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


class POWER_SEQUENCER_OT_make_still_image(bpy.types.Operator):
class POWER_SEQUENCER_OT_make_hold_frame(bpy.types.Operator):
"""
*brief* Make still image from active strip
*brief* Make a hold frame from the active strip
Converts image under the cursor to a still image, to create a pause effect in the video,
Converts the image under the cursor to a hold frame, to create a pause effect in the video,
using the active sequence
"""

@@ -36,7 +37,7 @@ class POWER_SEQUENCER_OT_make_still_image(bpy.types.Operator):

@classmethod
def poll(cls, context):
return context.selected_sequences
return context.scene.sequence_editor.active_strip.type in SequenceTypes.VIDEO

def invoke(self, context, event):
window_manager = context.window_manager
@@ -48,56 +49,42 @@ def execute(self, context):
sequencer = bpy.ops.sequencer
transform = bpy.ops.transform

start_frame = scene.frame_current
offset = convert_duration_to_frames(context, self.strip_duration)
frame_start = scene.frame_current

if active.type not in SequenceTypes.VIDEO:
self.report(
{"ERROR_INVALID_INPUT"},
"You must select a video or meta strip. \
You selected a strip of type"
+ str(active.type)
+ " instead.",
)
return {"CANCELLED"}

if not active.frame_final_start <= start_frame < active.frame_final_end:
self.report(
{"ERROR_INVALID_INPUT"},
"Your time cursor must be on the frame you want \
to convert to a still image.",
)
return {"CANCELLED"}

if start_frame == active.frame_final_start:
scene.frame_current = start_frame + 1
if not active.frame_final_start <= frame_start < active.frame_final_end:
return {"FINISHED"}

if self.strip_duration <= 0.0:
strips = sorted(
scene.sequence_editor.sequences, key=operator.attrgetter("frame_final_start")
)
if frame_start == active.frame_final_start:
scene.frame_current = frame_start + 1

for s in strips:
if s.frame_final_start > active.frame_final_start and s.channel == active.channel:
next = s
break
offset = next.frame_final_start - active.frame_final_end
# Detect the gap automatically
offset = convert_duration_to_frames(context, self.strip_duration)
if self.strip_duration <= 0.0:
try:
next_strip_start = next(
s
for s in sorted(context.sequences, key=operator.attrgetter("frame_final_start"))
if s.frame_final_start > active.frame_final_end
).frame_final_start
offset = next_strip_start - active.frame_final_end
except Exception:
pass

active.select = True
source_blend_type = active.blend_type
sequencer.cut(frame=scene.frame_current, type="SOFT", side="RIGHT")
sequencer.split(frame=scene.frame_current, type="SOFT", side="RIGHT")
transform.seq_slide(value=(offset, 0))
sequencer.cut(frame=scene.frame_current + offset + 1, type="SOFT", side="LEFT")
sequencer.split(frame=scene.frame_current + offset + 1, type="SOFT", side="LEFT")
transform.seq_slide(value=(-offset, 0))

sequencer.meta_make()
active = scene.sequence_editor.active_strip
active.name = "Still image"
active.name = "Hold frame"
active.blend_type = source_blend_type
active.select_right_handle = True
transform.seq_slide(value=(offset, 0))

scene.frame_current = start_frame
scene.frame_current = frame_start

active.select = True
active.select_right_handle = False
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -13,7 +15,7 @@ class POWER_SEQUENCER_OT_marker_delete_closest(bpy.types.Operator):
"demo": "",
"description": doc_description(__doc__),
"shortcuts": [],
"keymap": "Sequencer",
"keymap": "Markers",
}
bl_idname = doc_idname(__qualname__)
bl_label = doc["name"]
@@ -22,7 +24,7 @@ class POWER_SEQUENCER_OT_marker_delete_closest(bpy.types.Operator):

@classmethod
def poll(cls, context):
return len(context.scene.timeline_markers) > 0
return context.scene.timeline_markers

def invoke(self, context, event):
markers = context.scene.timeline_markers
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -12,8 +14,8 @@ class POWER_SEQUENCER_OT_marker_delete_direct(bpy.types.Operator):
"name": doc_name(__qualname__),
"demo": "",
"description": doc_description(__doc__),
"shortcuts": [],
"keymap": "Sequencer",
"shortcuts": [({"type": "X", "value": "PRESS"}, {}, "Delete Markers Instantly")],
"keymap": "Markers",
}
bl_idname = doc_idname(__qualname__)
bl_label = doc["name"]
@@ -22,7 +24,7 @@ class POWER_SEQUENCER_OT_marker_delete_direct(bpy.types.Operator):

@classmethod
def poll(cls, context):
return len(context.scene.timeline_markers) > 0
return context.scene.timeline_markers

def execute(self, context):
markers = context.scene.timeline_markers

This file was deleted.

@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
import datetime as dt
import operator as op
from .utils import pyperclip

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description

@@ -33,15 +33,19 @@ def execute(self, context):
self.report({"INFO"}, "No markers found")
return {"CANCELLED"}

sorted_markers = sorted(context.scene.timeline_markers, key=op.attrgetter("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
seconds_in_hour = 3600.0
time_format = "%H:%M:%S" if last_marker_seconds >= seconds_in_hour else "%M:%S"

markers_as_timecodes = []
for marker in sorted_markers:
framerate = render.fps / render.fps_base
time = dt.datetime(year=1, month=1, day=1) + dt.timedelta(
seconds=marker.frame / framerate
)
markers_as_timecodes.append(time.strftime("%H:%M:%S {}".format(marker.name)))
markers_as_timecodes.append(time.strftime(time_format) + " " + marker.name)

pyperclip.copy("\n".join(markers_as_timecodes))
bpy.context.window_manager.clipboard = "\n".join(markers_as_timecodes)
return {"FINISHED"}
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -23,7 +25,7 @@ class POWER_SEQUENCER_OT_markers_create_from_selected_strips(bpy.types.Operator)

@classmethod
def poll(cls, context):
return context.selected_sequences and len(context.selected_sequences) > 0
return context.selected_sequences

def execute(self, context):
cursor_frame_start = context.scene.frame_current
@@ -1,6 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.find_neighboring_markers import find_neighboring_markers
from .utils.functions import find_neighboring_markers
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


@@ -47,7 +49,7 @@ def invoke(self, context, event):
context.scene.sequence_editor.sequences, key=attrgetter("frame_final_end")
).frame_final_end

from .utils.sequences import set_preview_range
from .utils.functions import set_preview_range

set_preview_range(context, frame_start, frame_end)
return {"FINISHED"}
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -22,7 +24,7 @@ class POWER_SEQUENCER_OT_markers_snap_matching_strips(bpy.types.Operator):

@classmethod
def poll(cls, context):
return len(context.scene.timeline_markers) > 0
return context.scene.timeline_markers

def execute(self, context):
timeline_markers = context.scene.timeline_markers
@@ -1,5 +1,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
from .utils.get_frame_range import get_frame_range
from .utils.functions import get_frame_range

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description

@@ -26,14 +28,10 @@ class POWER_SEQUENCER_OT_meta_resize_to_content(bpy.types.Operator):

@classmethod
def poll(cls, context):
try:
next(s for s in context.selected_sequences if s.type == "META")
except StopIteration:
return False
return context.sequences
return context.selected_sequences

def execute(self, context):
selected_meta_strips = (s for s in context.selected_sequences if s.type == "META")
for s in selected_meta_strips:
s.frame_final_start, s.frame_final_end = get_frame_range(context, s.sequences)
s.frame_final_start, s.frame_final_end = get_frame_range(s.sequences)
return {"FINISHED"}
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
from .utils.global_settings import SequenceTypes

@@ -23,11 +25,7 @@ class POWER_SEQUENCER_OT_meta_trim_content_to_bounds(bpy.types.Operator):

@classmethod
def poll(cls, context):
try:
next(s for s in context.selected_sequences if s.type == "META")
return context.selected_sequences
except StopIteration:
return False
return context.selected_sequences

def execute(self, context):
to_delete = []
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -28,11 +30,7 @@ class POWER_SEQUENCER_OT_meta_ungroup_and_trim(bpy.types.Operator):

@classmethod
def poll(cls, context):
try:
next(s for s in context.selected_sequences if s.type == "META")
return context.selected_sequences
except StopIteration:
return False
return context.selected_sequences

def execute(self, context):
meta_strips = [s for s in context.selected_sequences if s.type == "META"]

This file was deleted.

@@ -1,8 +1,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
"""Toggle mute a sequence as you click on it"""
import bpy
from math import floor

from .utils.find_strips_mouse import find_strips_mouse
from .utils.functions import find_strips_mouse
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


@@ -27,7 +29,7 @@ class POWER_SEQUENCER_OT_mouse_toggle_mute(bpy.types.Operator):

@classmethod
def poll(cls, context):
return context is not None
return context.sequences

def invoke(self, context, event):
sequencer = bpy.ops.sequencer

This file was deleted.

@@ -0,0 +1,99 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
from math import floor

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


class POWER_SEQUENCER_OT_mouse_trim_instantly(bpy.types.Operator):
"""
*brief* Trim strip from a start to an end frame instantly
Trims a frame range or a selection from a start to an end frame.
If there's no precise time range, auto trims based on the closest cut
Args:
- frame_start and frame_end (int) define the frame range to trim
"""

doc = {
"name": doc_name(__qualname__),
"demo": "",
"description": doc_description(__doc__),
"shortcuts": [
(
{"type": "RIGHTMOUSE", "value": "PRESS", "ctrl": True, "alt": True},
{"select_mode": "CONTEXT"},
"Trim strip, keep gap",
),
(
{"type": "RIGHTMOUSE", "value": "PRESS", "ctrl": True, "alt": True, "shift": True},
{"select_mode": "CURSOR"},
"Trim strip, remove gap",
),
],
"keymap": "Sequencer",
}
bl_idname = doc_idname(__qualname__)
bl_label = doc["name"]
bl_description = doc_brief(doc["description"])
bl_options = {"REGISTER", "UNDO"}

select_mode: bpy.props.EnumProperty(
items=[
("CONTEXT", "Smart", "Uses the selection if possible, else uses the other modes"),
("CURSOR", "Time cursor", "Select all of the strips the time cursor overlaps"),
],
name="Selection mode",
description="Auto-select the strip you click on or that the time cursor overlaps",
default="CONTEXT",
)
select_linked: bpy.props.BoolProperty(
name="Use linked time",
description="If auto-select, cut linked strips if checked",
default=False,
)
gap_remove: bpy.props.BoolProperty(
name="Remove gaps",
description="When trimming the sequences, remove gaps automatically",
default=True,
)

@classmethod
def poll(cls, context):
return context.sequences

def invoke(self, context, event):
to_trim = []
frame, channel = -1, -1
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_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
]
if not to_trim:
return {"FINISHED"}

frame_cut_closest = min(get_frame_range(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, to_trim=to_trim)

context.scene.frame_current = frame

if self.gap_remove and self.select_mode == "CURSOR":
bpy.ops.power_sequencer.gap_remove(frame=frame_start, move_time_cursor=True)

return {"FINISHED"}

Large diffs are not rendered by default.

@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
import os
from platform import system
@@ -25,7 +27,7 @@ class POWER_SEQUENCER_OT_open_project_directory(bpy.types.Operator):

@classmethod
def poll(cls, context):
return context.scene
return bpy.data.is_saved

def execute(self, context):
path = os.path.split(bpy.data.filepath)[0]

This file was deleted.

This file was deleted.

@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -13,10 +15,9 @@ class POWER_SEQUENCER_OT_playback_speed_set(bpy.types.Operator):
"demo": "",
"description": doc_description(__doc__),
"shortcuts": [
({"type": "ONE", "value": "PRESS"}, {"speed": "normal"}, "Speed to 1x"),
({"type": "TWO", "value": "PRESS"}, {"speed": "fast"}, "Speed to 1.33x"),
({"type": "THREE", "value": "PRESS"}, {"speed": "faster"}, "Speed to 1.66x"),
({"type": "FOUR", "value": "PRESS"}, {"speed": "double"}, "Speed to 2x"),
({"type": "ONE", "ctrl": True, "value": "PRESS"}, {"speed": "NORMAL"}, "Speed to 1x"),
({"type": "TWO", "ctrl": True, "value": "PRESS"}, {"speed": "DOUBLE"}, "Speed to 2x"),
({"type": "THREE", "ctrl": True, "value": "PRESS"}, {"speed": "TRIPLE"}, "Speed to 3x"),
],
"keymap": "Sequencer",
}
@@ -27,15 +28,13 @@ class POWER_SEQUENCER_OT_playback_speed_set(bpy.types.Operator):

speed: bpy.props.EnumProperty(
items=[
("normal", "Normal (1x)", ""),
("fast", "Fast (1.33x)", ""),
("faster", "Faster (1.66x)", ""),
("double", "Double (2x)", ""),
("triple", "Triple (3x)", ""),
("NORMAL", "Normal (1x)", ""),
("DOUBLE", "Double (2x)", ""),
("TRIPLE", "Triple (3x)", ""),
],
name="Speed",
description="Change the playback speed",
default="double",
default="DOUBLE",
)

@classmethod
@@ -1,8 +1,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.get_frame_range import get_frame_range
from .utils.set_preview_range import set_preview_range
from .utils.convert_duration_to_frames import convert_duration_to_frames
from .utils.functions import get_frame_range
from .utils.functions import set_preview_range
from .utils.functions import convert_duration_to_frames
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


@@ -43,7 +45,7 @@ class POWER_SEQUENCER_OT_preview_closest_cut(bpy.types.Operator):

@classmethod
def poll(cls, context):
return context.sequences and len(context.sequences) > 0
return context.sequences

def execute(self, context):
scene = context.scene
@@ -61,7 +63,7 @@ def execute(self, context):
return {"CANCELLED"}

if scene.frame_preview_start == start and scene.frame_preview_end == end:
start, end = get_frame_range(context, context.sequences)
start, end = get_frame_range(context.sequences)
set_preview_range(context, start, end)
return {"FINISHED"}

@@ -1,17 +1,19 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.get_frame_range import get_frame_range
from .utils.set_preview_range import set_preview_range
from .utils.functions import get_frame_range
from .utils.functions import set_preview_range
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


class POWER_SEQUENCER_OT_preview_to_selection(bpy.types.Operator):
"""
*brief* Sets the timeline preview range to that of the selected sequences
*brief* Sets the timeline preview range to match the selection
Sets the scene frame start to the earliest frame start of selected sequences and the scene
frame end to the last frame of selected sequences.
Uses all sequences in the current context if no sequences are selected.
"""

doc = {
@@ -30,16 +32,14 @@ class POWER_SEQUENCER_OT_preview_to_selection(bpy.types.Operator):

@classmethod
def poll(cls, context):
return context.selected_sequences
return context.sequences

def execute(self, context):
scene = context.scene
frame_start, frame_end = get_frame_range(
context, sequences=context.selected_sequences, get_from_start=False
sequences = (
context.selected_sequences
if len(context.selected_sequences) >= 1
else context.sequences
)

if scene.frame_start == frame_start and scene.frame_end == frame_end:
frame_start, frame_end = get_frame_range(context, sequences=[], get_from_start=True)

frame_start, frame_end = get_frame_range(sequences)
set_preview_range(context, frame_start, frame_end - 1)
return {"FINISHED"}
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
import os

@@ -6,7 +8,14 @@

class POWER_SEQUENCER_OT_render_apply_preset(bpy.types.Operator):
"""
Set rendering, and encoding settings, and the output filename based on a preset
*Brief* Applies a rendering preset to the project
Sets rendering and encoding settings and an output filename based on a preset.
Available presets:
- YouTube: 1080p mp4 video encoded with H264 and AAC for audio, based on YouTube's recommended settings
- Twitter: 720p mp4 video
"""

doc = {
@@ -1,31 +1,33 @@
import bpy
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
if __name__ == "__main__":
import bpy

# Resolution
bpy.context.scene.render.resolution_x = 1280
bpy.context.scene.render.resolution_y = 720
bpy.context.scene.render.resolution_percentage = 100
bpy.context.scene.render.pixel_aspect_x = 1
bpy.context.scene.render.pixel_aspect_y = 1
render = bpy.context.scene.render
render.resolution_x = 1280
render.resolution_y = 720
render.resolution_percentage = 100
render.pixel_aspect_x = 1
render.pixel_aspect_y = 1

# FFMPEG
bpy.context.scene.render.image_settings.file_format = "FFMPEG"
bpy.context.scene.render.ffmpeg.format = "MPEG4"
bpy.context.scene.render.ffmpeg.codec = "H264"
# FFMPEG
render.image_settings.file_format = "FFMPEG"
render.ffmpeg.format = "MPEG4"
render.ffmpeg.codec = "H264"

bpy.context.scene.render.ffmpeg.constant_rate_factor = "HIGH"
bpy.context.scene.render.ffmpeg.ffmpeg_preset = "MEDIUM"
render.ffmpeg.constant_rate_factor = "HIGH"
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

is_ntsc = bpy.context.scene.render.fps != 25
if is_ntsc:
bpy.context.scene.render.ffmpeg.gopsize = 18
else:
bpy.context.scene.render.ffmpeg.gopsize = 15
bpy.context.scene.render.ffmpeg.use_max_b_frames = False

bpy.context.scene.render.ffmpeg.video_bitrate = 4000
bpy.context.scene.render.ffmpeg.maxrate = 4000
bpy.context.scene.render.ffmpeg.minrate = 0
bpy.context.scene.render.ffmpeg.buffersize = 224 * 8
bpy.context.scene.render.ffmpeg.packetsize = 2048
bpy.context.scene.render.ffmpeg.muxrate = 10080000
render.ffmpeg.video_bitrate = 4000
render.ffmpeg.maxrate = 4000
render.ffmpeg.minrate = 0
render.ffmpeg.buffersize = 224 * 8
render.ffmpeg.packetsize = 2048
render.ffmpeg.muxrate = 10080000
@@ -1,31 +1,34 @@
import bpy
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
if __name__ == "__main__":
import bpy

# Resolution
bpy.context.scene.render.resolution_x = 1920
bpy.context.scene.render.resolution_y = 1080
bpy.context.scene.render.resolution_percentage = 100
bpy.context.scene.render.pixel_aspect_x = 1
bpy.context.scene.render.pixel_aspect_y = 1
render = bpy.context.scene.render
render.resolution_x = 1920
render.resolution_y = 1080
render.resolution_percentage = 100
render.pixel_aspect_x = 1
render.pixel_aspect_y = 1

# FFMPEG
bpy.context.scene.render.image_settings.file_format = "FFMPEG"
bpy.context.scene.render.ffmpeg.format = "MPEG4"
bpy.context.scene.render.ffmpeg.codec = "H264"
render.image_settings.file_format = "FFMPEG"
render.ffmpeg.format = "MPEG4"
render.ffmpeg.codec = "H264"

bpy.context.scene.render.ffmpeg.constant_rate_factor = "PERC_LOSSLESS"
bpy.context.scene.render.ffmpeg.ffmpeg_preset = "GOOD"
render.ffmpeg.constant_rate_factor = "PERC_LOSSLESS"
render.ffmpeg.ffmpeg_preset = "BEST"

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

is_ntsc = bpy.context.scene.render.fps != 25
if is_ntsc:
bpy.context.scene.render.ffmpeg.gopsize = 18
else:
bpy.context.scene.render.ffmpeg.gopsize = 15
bpy.context.scene.render.ffmpeg.use_max_b_frames = False
render.ffmpeg.video_bitrate = 9000
render.ffmpeg.maxrate = 9000
render.ffmpeg.minrate = 0
render.ffmpeg.buffersize = 224 * 8
render.ffmpeg.packetsize = 2048
render.ffmpeg.muxrate = 10080000

bpy.context.scene.render.ffmpeg.video_bitrate = 9000
bpy.context.scene.render.ffmpeg.maxrate = 9000
bpy.context.scene.render.ffmpeg.minrate = 0
bpy.context.scene.render.ffmpeg.buffersize = 224 * 8
bpy.context.scene.render.ffmpeg.packetsize = 2048
bpy.context.scene.render.ffmpeg.muxrate = 10080000
render.ffmpeg.audio_codec = "AAC"
render.ffmpeg.audio_bitrate = 384
@@ -1,11 +1,14 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
from operator import attrgetter

from .utils.doc import doc_brief, doc_description, doc_idname, doc_name
from .utils.get_frame_range import get_frame_range
from .utils.get_mouse_view_coords import get_mouse_frame_and_channel
from .utils.global_settings import SequenceTypes
from .utils.slice_contiguous_sequence_list 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):
@@ -42,6 +45,7 @@ def execute(self, context):
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
@@ -51,44 +55,28 @@ def execute(self, context):

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(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
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
from operator import attrgetter

@@ -33,39 +35,45 @@ class POWER_SEQUENCER_OT_scene_create_from_selection(bpy.types.Operator):

@classmethod
def poll(cls, context):
return context.selected_sequences and len(context.selected_sequences) > 0
return context.selected_sequences

def execute(self, context):
start_scene_name = context.scene.name

selection = context.selected_sequences
selection_start_frame = min(
selection, key=attrgetter("frame_final_start")
).frame_final_start
selection_start_channel = min(selection, key=attrgetter("channel")).channel
if len(context.selected_sequences) != 0:
selection = context.selected_sequences[:]
selection_start_frame = min(
selection, key=attrgetter("frame_final_start")
).frame_final_start
selection_start_channel = min(selection, key=attrgetter("channel")).channel

# Create new scene for the scene strip
bpy.ops.scene.new(type="FULL_COPY")
new_scene_name = context.scene.name
# Create new scene for the scene strip
bpy.ops.scene.new(type="FULL_COPY")

bpy.ops.sequencer.select_all(action="INVERT")
bpy.ops.power_sequencer.delete_direct()
frame_offset = selection_start_frame - 1
for s in context.sequences:
try:
s.frame_start -= frame_offset
except Exception:
continue
bpy.ops.sequencer.select_all()
bpy.ops.power_sequencer.preview_to_selection()
context.window.scene.name = context.selected_sequences[0].name
new_scene_name = context.window.scene.name

# Back to start scene
context.screen.scene = bpy.data.scenes[start_scene_name]
###after full copy also unselected strips are in the sequencer... Delete those strips
bpy.ops.sequencer.select_all(action="INVERT")
bpy.ops.power_sequencer.delete_direct()
frame_offset = selection_start_frame - 1
for s in context.sequences:
try:
s.frame_start -= frame_offset
except Exception:
continue
bpy.ops.sequencer.select_all()
bpy.ops.power_sequencer.preview_to_selection()

bpy.ops.power_sequencer.delete_direct()
bpy.ops.sequencer.scene_strip_add(
frame_start=selection_start_frame, channel=selection_start_channel, scene=new_scene_name
)
scene_strip = context.selected_sequences[0]
scene_strip.use_sequence = True
# Back to start scene
bpy.context.window.scene = bpy.data.scenes[start_scene_name]

bpy.ops.power_sequencer.delete_direct()
bpy.ops.sequencer.scene_strip_add(
frame_start=selection_start_frame,
channel=selection_start_channel,
scene=new_scene_name,
)
scene_strip = context.selected_sequences[0]
# scene_strip.use_sequence = True
return {"FINISHED"}
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -22,7 +24,7 @@ class POWER_SEQUENCER_OT_scene_cycle(bpy.types.Operator):

@classmethod
def poll(cls, context):
return len(bpy.data.scenes) > 1
return bpy.data.scenes

def execute(self, context):
scenes = bpy.data.scenes
@@ -1,17 +1,17 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from bpy.props import BoolProperty
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


class POWER_SEQUENCER_OT_merge_from_scene_strip(bpy.types.Operator):
"""
*brief* Copies all sequences and markers from a SceneStrip's scene into
the active scene. Optionally delete the source scene and the strip.
the active scene. Optionally delete the source scene and the strip
WARNING: Currently the operator doesn't recreate any animation data,
be careful by choosing to delete the scene after the merge.
be careful by choosing to delete the scene after the merge
"""

doc = {
@@ -26,22 +26,24 @@ class POWER_SEQUENCER_OT_merge_from_scene_strip(bpy.types.Operator):
bl_description = doc_brief(doc["description"])
bl_options = {"REGISTER", "UNDO"}

delete_scene = BoolProperty(
delete_scene: bpy.props.BoolProperty(
name="Delete Strip's scene",
description="Delete the SceneStrip's scene after the merging",
default=True,
)

@classmethod
def poll(cls, context):
return context.scene.sequence_editor.active_strip.type == "SCENE"
return context.scene.sequence_editor.active_strip

def invoke(self, context, event):
window_manager = context.window_manager
return window_manager.invoke_props_dialog(self)

def execute(self, context):
strip = context.scene.sequence_editor.active_strip
if strip.type != "SCENE":
return {"FINISHED"}
strip_scene = strip.scene
start_scene = context.window.scene

@@ -57,34 +59,42 @@ def execute(self, context):
context.window.scene = strip_scene
bpy.ops.scene.delete()
context.window.scene = start_scene
self.report(type={"WARNING"}, message="All animations on source scene were lost")
self.report(type={"WARNING"}, message="Merged scenes lose all their animation data.")

return {"FINISHED"}

def merge_strips(self, context, source_scene, target_scene):
context.window.scene = source_scene
current_frame = context.scene.frame_current
context.scene.frame_current = context.scene.frame_start
bpy.ops.sequencer.select_all(action="SELECT")
bpy.ops.sequencer.copy()
context.scene.frame_current = current_frame

context.window.scene = target_scene
current_frame = context.scene.frame_current
active = context.scene.sequence_editor.active_strip
context.scene.frame_current = active.frame_final_start
context.scene.frame_current = active.frame_start
bpy.ops.sequencer.select_all(action="DESELECT")
bpy.ops.sequencer.paste()

context.scene.frame_current = current_frame

def merge_markers(self, source_scene, target_scene):
def merge_markers(self, context, source_scene, target_scene):
if len(source_scene.timeline_markers) == 0:
return

if len(target_scene.timeline_markers) > 0:
bpy.ops.marker.select_all(action="DESELECT")

bpy.context.screen.scene = source_scene
bpy.context.window.scene = source_scene
bpy.ops.marker.select_all(action="SELECT")
bpy.ops.marker.make_links_scene(scene=target_scene.name)

bpy.context.screen.scene = target_scene
active = bpy.context.screen.scene.sequence_editor.active_strip
time_offset = active.frame_final_start
bpy.context.window.scene = target_scene
active = bpy.context.window.scene.sequence_editor.active_strip

# Offset to account for source scenes starting on any frame.
time_offset = active.frame_start - source_scene.frame_start
bpy.ops.marker.move(frames=time_offset)
bpy.ops.marker.select_all(action="DESELECT")
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -25,10 +27,13 @@ class POWER_SEQUENCER_OT_open_scene_strip(bpy.types.Operator):

@classmethod
def poll(cls, context):
return context.scene.sequence_editor.active_strip.type == "SCENE"
return context.scene.sequence_editor.active_strip

def execute(self, context):
strip_scene = context.scene.sequence_editor.active_strip.scene
context.screen.scene = bpy.data.scenes[strip_scene.name]
active_strip = context.scene.sequence_editor.active_strip
if active_strip.type != "SCENE":
return {"FINISHED"}

strip_scene = active_strip.scene
context.window.scene = bpy.data.scenes[strip_scene.name]
return {"FINISHED"}
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
import operator

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description

@@ -30,22 +31,15 @@ class POWER_SEQUENCER_OT_scene_rename_with_strip(bpy.types.Operator):

@classmethod
def poll(cls, context):
if context.selected_sequences == None or len(context.selected_sequences) < 1:
return False

is_all_scene = True
for sequence in context.selected_sequences:
if not sequence.type == "SCENE":
is_all_scene = False
return is_all_scene
return context.selected_sequences

def invoke(self, context, event):
window_manager = context.window_manager
return window_manager.invoke_props_dialog(self)

def execute(self, context):

for strip in context.selected_sequences:
scene_strips = [s for s in context.selected_sequences if s.type == "SCENE"]
for strip in scene_strips:
strip.name = self.new_name
strip.scene.name = strip.name
return {"FINISHED"}
@@ -0,0 +1,57 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


class POWER_SEQUENCER_OT_select_all_left_or_right(bpy.types.Operator):
"""
*Brief* Selects all strips left or right of the time cursor
"""

doc = {
"name": doc_name(__qualname__),
"demo": "",
"description": doc_description(__doc__),
"shortcuts": [
(
{"type": "Q", "value": "PRESS", "shift": True},
{"side": "LEFT"},
"Select all strips to the LEFT of the time cursor",
),
(
{"type": "E", "value": "PRESS", "shift": True},
{"side": "RIGHT"},
"Select all strips to the right of the time cursor",
),
],
"keymap": "Sequencer",
}
bl_idname = doc_idname(__qualname__)
bl_label = doc["name"]
bl_description = doc_brief(doc["description"])
bl_options = {"REGISTER", "UNDO"}

side: bpy.props.EnumProperty(
name="Side",
description=("Side to select"),
items=[
("LEFT", "Left", "Move strips back in time, to the left"),
("RIGHT", "Right", "Move strips forward in time, to the right"),
],
default="LEFT",
)

@classmethod
def poll(cls, context):
return context.sequences

def execute(self, context):
if self.side == "LEFT":
for s in context.sequences:
s.select = s.frame_final_end < context.scene.frame_current
else:
for s in context.sequences:
s.select = s.frame_final_start > context.scene.frame_current
return {"FINISHED"}
@@ -1,42 +1,44 @@
import bpy

from .utils.find_strips_mouse import find_strips_mouse
from .utils.get_mouse_view_coords import get_mouse_frame_and_channel
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


class POWER_SEQUENCER_OT_select_closest_to_mouse(bpy.types.Operator):
"""
Select the closest strip under the mouse cursor
"""

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"}

frame: bpy.props.IntProperty(name="Frame")
channel: bpy.props.IntProperty(name="Channel")

@classmethod
def poll(cls, context):
return context.sequences

def invoke(self, context, event):
self.frame, self.channel = get_mouse_frame_and_channel(context, event)
return self.execute(context)

def execute(self, context):
try:
strip = find_strips_mouse(context, self.frame, self.channel)[0]
strip.select = True
except Exception:
return {"CANCELLED"}
return {"FINISHED"}
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.functions import find_strips_mouse
from .utils.functions import get_mouse_frame_and_channel
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


class POWER_SEQUENCER_OT_select_closest_to_mouse(bpy.types.Operator):
"""
Select the closest strip under the mouse cursor
"""

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"}

frame: bpy.props.IntProperty(name="Frame")
channel: bpy.props.IntProperty(name="Channel")

@classmethod
def poll(cls, context):
return context.sequences

def invoke(self, context, event):
self.frame, self.channel = get_mouse_frame_and_channel(context, event)
return self.execute(context)

def execute(self, context):
try:
strip = find_strips_mouse(context, self.frame, self.channel)[0]
strip.select = True
except Exception:
return {"CANCELLED"}
return {"FINISHED"}
@@ -1,6 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.find_linked_sequences import find_linked
from .utils.functions import find_linked
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -23,7 +25,7 @@ class POWER_SEQUENCER_OT_select_linked_strips(bpy.types.Operator):

@classmethod
def poll(cls, context):
return context.scene.sequence_editor is not None
return context.scene.sequence_editor

def execute(self, context):
# save current selection first
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.global_settings import SequenceTypes
@@ -36,7 +38,7 @@ class POWER_SEQUENCER_OT_select_related_strips(bpy.types.Operator):

@classmethod
def poll(cls, context):
return len(context.selected_sequences) > 0
return context.selected_sequences

def execute(self, context):
if self.find_all:
@@ -1,5 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.functions import get_sequences_under_cursor
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


@@ -20,26 +23,15 @@ class POWER_SEQUENCER_OT_select_strips_under_cursor(bpy.types.Operator):
bl_description = doc_brief(doc["description"])
bl_options = {"REGISTER", "UNDO"}

locked: bpy.props.BoolProperty(name="Locked")
deselect_first: bpy.props.BoolProperty(name="Deselect First")

@classmethod
def poll(cls, context):
return len(context.sequences) > 0
return context.sequences

def execute(self, context):
bpy.ops.sequencer.select_all(action="DESELECT")
current_frame = context.scene.frame_current
sequences_to_select = []
for s in context.sequences:
if (
s.frame_final_start <= current_frame
and s.frame_final_end >= current_frame
and not s.lock
or self.locked
):
sequences_to_select.append(s)
if not sequences_to_select:
return {"CANCELLED"}
for s in sequences_to_select:
if self.deselect_first:
bpy.ops.sequencer.select_all(action="DESELECT")
for s in get_sequences_under_cursor(context):
s.select = True
return {"FINISHED"}
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -0,0 +1,48 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.functions import get_sequences_under_cursor
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


class POWER_SEQUENCER_OT_snap(bpy.types.Operator):
"""
*Brief* Snaps selected strips to the time cursor ignoring locked sequences.
Automatically selects sequences if there is no active selection
"""

doc = {
"name": doc_name(__qualname__),
"demo": "",
"description": doc_description(__doc__),
"shortcuts": [
(
{"type": "S", "value": "PRESS", "shift": True},
{},
"Snap sequences to cursor",
)
],
"keymap": "Sequencer",
}
bl_idname = doc_idname(__qualname__)
bl_label = doc["name"]
bl_description = doc_brief(doc["description"])
bl_options = {"REGISTER", "UNDO"}

@classmethod
def poll(cls, context):
return context.sequences

def execute(self, context):
sequences = (
context.selected_sequences
if len(context.selected_sequences) > 0
else get_sequences_under_cursor(context)
)
frame = context.scene.frame_current
for s in sequences:
s.select = True
bpy.ops.sequencer.snap(frame=frame)
return {"FINISHED"}
@@ -0,0 +1,47 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
from .utils.functions import get_sequences_under_cursor, move_selection
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


class POWER_SEQUENCER_OT_snap_selection(bpy.types.Operator):
"""
*Brief* Snap the entire selection to the time cursor.
Automatically selects sequences if there is no active selection.
To snap each strip individually, see Snap
"""

doc = {
"name": doc_name(__qualname__),
"demo": "",
"description": doc_description(__doc__),
"shortcuts": [
(
{"type": "S", "value": "PRESS", "alt": True},
{},
"Snap selection to cursor",
)
],
"keymap": "Sequencer",
}
bl_idname = doc_idname(__qualname__)
bl_label = doc["name"]
bl_description = doc_brief(doc["description"])
bl_options = {"REGISTER", "UNDO"}

@classmethod
def poll(cls, context):
return context.sequences

def execute(self, context):
sequences = (
context.selected_sequences
if context.selected_sequences
else get_sequences_under_cursor(context)
)
frame_first = min(sequences, key=lambda s: s.frame_final_start).frame_final_start
time_offset = context.scene.frame_current - frame_first
move_selection(context, sequences, time_offset)
return {"FINISHED"}

This file was deleted.

@@ -0,0 +1,52 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

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


class POWER_SEQUENCER_OT_space_sequences(bpy.types.Operator):
"""
*brief* Offsets all strips to the right of the time cursor by a given duration, ignoring locked sequences
"""

doc = {
"name": doc_name(__qualname__),
"demo": "",
"description": doc_description(__doc__),
"shortcuts": [({"type": "EQUAL", "value": "PRESS"}, {}, "")],
"keymap": "Sequencer",
}
bl_idname = doc_idname(__qualname__)
bl_label = doc["name"]
bl_description = doc_brief(doc["description"])
bl_options = {"REGISTER", "UNDO"}

gap_to_insert: bpy.props.FloatProperty(
name="Duration", description="The time offset to apply to the strips", default=1.0
)

@classmethod
def poll(cls, context):
return context.sequences

def invoke(self, context, event):
sequences = [
s
for s in context.sequences
if s.type in SequenceTypes.CUTABLE
and s.frame_final_start >= context.scene.frame_current
and not s.lock
]

gap_frames = convert_duration_to_frames(context, self.gap_to_insert)
sorted_sequences = sorted(sequences, key=lambda s: s.frame_final_start, reverse=True)
for s in sorted_sequences:
s.frame_start += gap_frames

markers = context.scene.timeline_markers
for m in [m for m in markers if m.frame >= context.scene.frame_current]:
m.frame += gap_frames
return {"FINISHED"}
@@ -1,9 +1,11 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


class POWER_SEQUENCER_OT_unspeed(bpy.types.Operator):
class POWER_SEQUENCER_OT_speed_remove_effect(bpy.types.Operator):
"""
*brief* Removes speed from META, un-groups META
@@ -26,15 +28,7 @@ class POWER_SEQUENCER_OT_unspeed(bpy.types.Operator):

@classmethod
def poll(cls, context):
result = False
try:
scene = context.scene
active_strip = scene.sequence_editor.active_strip
result = active_strip.select and active_strip.type == "META"
result = result and [s for s in active_strip.sequences if s.type == "SPEED"]
except AttributeError:
pass
return result
return context.scene.sequence_editor.active_strip.type == "META"

def execute(self, context):
active = context.scene.sequence_editor.active_strip
@@ -1,9 +1,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
from math import ceil

from .utils.global_settings import SequenceTypes
from .utils.slice_contiguous_sequence_list import slice_selection
from .utils.find_linked_sequences import find_linked
from .utils.functions import slice_selection
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


@@ -19,7 +20,23 @@ class POWER_SEQUENCER_OT_speed_up_movie_strip(bpy.types.Operator):
"name": doc_name(__qualname__),
"demo": "https://i.imgur.com/ZyEd0jD.gif",
"description": doc_description(__doc__),
"shortcuts": [({"type": "PLUS", "value": "PRESS", "shift": True}, {}, "Add Speed")],
"shortcuts": [
(
{"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",
}
bl_idname = doc_idname(__qualname__)
@@ -46,7 +63,7 @@ def execute(self, context):
if not sequences:
self.report(
{"ERROR_INVALID_INPUT"},
"No Movie sequence or Metastrips selected. Operation cancelled",
"No Movie meta_strip or Metastrips selected. Operation cancelled",
)
return {"FINISHED"}

@@ -57,40 +74,38 @@ def execute(self, context):
selection_blocks = slice_selection(context, sequences)

for sequences in selection_blocks:
bpy.ops.sequencer.select_all(action="DESELECT")
self.speed_effect_add(context, sequences)

self.report(
{"INFO"}, "Successfully processed " + str(len(selection_blocks)) + " selection blocks"
)
return {"FINISHED"}

def speed_effect_add(self, context, sequences=[]):
def speed_effect_add(self, context, sequences):
if not sequences:
return

sequence_editor = context.scene.sequence_editor
sequence = None
if len(sequences) == 1:
sequence = sequence_editor.active_strip = sequences[0]
else:
for s in sequences:
s.select = True
bpy.ops.sequencer.meta_make()
sequence = sequence_editor.active_strip
sequencer = bpy.ops.sequencer

sequencer.select_all(action="DESELECT")
for s in sequences:
s.select = True
sequencer.meta_make()
meta_strip = sequence_editor.active_strip

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

duration = ceil(sequence.frame_final_duration / speed_effect.speed_factor)
sequence.frame_final_end = sequence.frame_final_start + duration
duration = ceil(meta_strip.frame_final_duration / speed_effect.speed_factor)
meta_strip.frame_final_end = meta_strip.frame_final_start + duration

sequence_editor.active_strip = sequence
bpy.ops.sequencer.select_all(action="DESELECT")
sequence.select = True
sequence_editor.active_strip = meta_strip
speed_effect.select = True
bpy.ops.sequencer.meta_make()

sequence_editor.active_strip.name = sequence.name + " " + str(self.speed_factor) + "x"
meta_strip.select = True
sequencer.meta_make()
sequence_editor.active_strip.name = (
meta_strip.sequences[0].name + " " + str(self.speed_factor) + "x"
)

Large diffs are not rendered by default.

This file was deleted.

@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.global_settings import SequenceTypes
@@ -1,11 +1,13 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy
from operator import attrgetter

from .utils.global_settings import SequenceTypes
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


class POWER_SEQUENCER_OT_crossfade_remove(bpy.types.Operator):
class POWER_SEQUENCER_OT_transitions_remove(bpy.types.Operator):
"""
Delete a crossfade strip and moves the handles of the input strips to form a cut again
"""
@@ -26,19 +28,20 @@ class POWER_SEQUENCER_OT_crossfade_remove(bpy.types.Operator):

@classmethod
def poll(cls, context):
return (
context.selected_sequences
and len([s for s in context.selected_sequences if s.type in SequenceTypes.TRANSITION])
> 0
)
return context.selected_sequences

def execute(self, context):
to_process = (
self.sequences_override if self.sequences_override else context.selected_sequences
)

transitions = [s for s in to_process if s.type in SequenceTypes.TRANSITION]
if not transitions:
return {"FINISHED"}

saved_selection = [
s for s in context.selected_sequences if s.type not in SequenceTypes.TRANSITION
]
bpy.ops.sequencer.select_all(action="DESELECT")
for transition in transitions:
effect_middle_frame = round(
@@ -48,7 +51,7 @@ def execute(self, context):
inputs = [transition.input_1, transition.input_2]
strips_to_edit = []
for input in inputs:
if input.type in SequenceTypes.EFFECT:
if input.type in SequenceTypes.EFFECT and hasattr(input, "input_1"):
strips_to_edit.append(input.input_1)
else:
strips_to_edit.append(input)
@@ -61,4 +64,7 @@ def execute(self, context):

transition.select = True
bpy.ops.sequencer.delete()

for s in saved_selection:
s.select = True
return {"FINISHED"}
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
from operator import attrgetter

import bpy
@@ -10,6 +12,7 @@ class POWER_SEQUENCER_OT_trim_left_or_right_handles(bpy.types.Operator):
Trims or extends the handle closest to the time cursor for all selected strips.
If you keep the Shift key down, the edit will ripple through the timeline.
Auto selects sequences under the time cursor when you don't have a selection
"""

doc = {
@@ -55,7 +58,7 @@ class POWER_SEQUENCER_OT_trim_left_or_right_handles(bpy.types.Operator):

@classmethod
def poll(cls, context):
return len(context.sequences) > 0
return context.sequences

def execute(self, context):
frame_current = context.scene.frame_current
@@ -1,6 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
import bpy

from .utils.get_mouse_view_coords import get_mouse_frame_and_channel
from .utils.functions import get_mouse_frame_and_channel
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description


@@ -1,17 +1,24 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
"""
Find the two closest cuts, trims and deletes all strips above in the range but leaves some
margin. Removes the newly formed gap.
"""
import bpy
from math import floor

from .utils.convert_duration_to_frames 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, find_strips_in_range


class POWER_SEQUENCER_OT_trim_to_surrounding_cuts(bpy.types.Operator):
"""
Trim to surrounding cuts
"""*Brief* Automatically trim to surrounding cuts with some time offset
Finds the two cuts closest to the mouse cursor and trims the footage in between, leaving a
little time offset. It's useful after you removed some bad audio but you need to keep some
video around for a transition.
By default, the tool leaves a 0.2 seconds margin on either side of the trim.
"""

doc = {
@@ -46,59 +53,32 @@ class POWER_SEQUENCER_OT_trim_to_surrounding_cuts(bpy.types.Operator):

@classmethod
def poll(cls, context):
return context is not None
return context

def invoke(self, context, event):
if not context.sequences:
return {"CANCELLED"}

sequencer = bpy.ops.sequencer

# Convert mouse position to frame, channel
x = context.region.view2d.region_to_view(x=event.mouse_region_x, y=event.mouse_region_y)[0]
frame = round(x)

left_cut_frame, right_cut_frame = self.find_closest_surrounding_cuts(context, frame)
surrounding_cut_frames_duration = abs(left_cut_frame - right_cut_frame)
frame = context.region.view2d.region_to_view(
x=event.mouse_region_x, y=event.mouse_region_y
)[0]
frame = round(frame)

margin_frame = convert_duration_to_frames(context, self.margin)

if surrounding_cut_frames_duration <= margin_frame * 2:
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"},
("The trim margin is larger than the gap\n" "Use snap trim or reduce the margin"),
)
return {"CANCELLED"}

strips_to_delete, strips_to_trim = self.find_strips_in_range(
context, left_cut_frame, right_cut_frame
to_delete, to_trim = find_strips_in_range(
left_cut_frame, right_cut_frame, context.sequences
)
trim_start, trim_end = (left_cut_frame + margin_frame, right_cut_frame - margin_frame)

# print("start: {!s}, end: {!s}".format(left_cut_frame, right_cut_frame))
# for s in strips_to_trim:
# print(s.name)

for s in strips_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")
strips_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 strips_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
@@ -107,71 +87,4 @@ def invoke(self, context, event):
bpy.ops.power_sequencer.gap_remove()
context.scene.frame_current = trim_start

# FIXME: Workaround Blender 2.80's audio bug, remove when fixed in Blender
for s in bpy.context.sequences:
if s.lock:
continue
s.select = True
bpy.ops.transform.seq_slide(value=(0, 0))
s.select = False
break
return {"FINISHED"}

def find_strips_in_range(
self, context, start_frame, end_frame, sequences=None, find_overlapping=True
):
"""
Returns strips which start and end within a certain frame range, or that overlap a
certain frame range
Args:
- start_frame, the start of the frame range
- end_frame, the end of the frame range
- sequences (optional): only work with these sequences.
If it doesn't receive any, the function works with all the sequences in the current context
- find_overlapping (optional): find and return a list of strips that overlap the
frame range
Returns a tuple of two lists:
[0], strips entirely in the frame range
[1], strips that only overlap the frame range
"""
strips_in_range = []
strips_overlapping_range = []
if not sequences:
sequences = context.sequences
for s in sequences:
if start_frame <= s.frame_final_start <= end_frame:
if start_frame <= s.frame_final_end <= end_frame:
strips_in_range.append(s)
elif find_overlapping:
strips_overlapping_range.append(s)
elif find_overlapping and start_frame <= s.frame_final_end <= end_frame:
strips_overlapping_range.append(s)
if s.frame_final_start < start_frame and s.frame_final_end > end_frame:
strips_overlapping_range.append(s)
return strips_in_range, strips_overlapping_range

def find_closest_surrounding_cuts(self, context, frame=0):
"""
Returns a tuple of (left_cut_frame, right_cut_frame) of the two closest cuts
surrounding a frame
Args:
- frame, find the closest cuts that surround this frame
"""
start_cut_frame, end_cut_frame = 1000000, 1000000
for s in context.sequences:
distance_to_start = abs(frame - s.frame_final_start)
distance_to_end = abs(frame - s.frame_final_end)

distance_to_start_cut_frame = abs(start_cut_frame - frame)
distance_to_end_cut_frame = abs(end_cut_frame - frame)

if s.frame_final_start < frame and distance_to_start < distance_to_start_cut_frame:
start_cut_frame = s.frame_final_start
if s.frame_final_end < frame and distance_to_end < distance_to_start_cut_frame:
start_cut_frame = s.frame_final_end
if s.frame_final_end > frame and distance_to_end < distance_to_end_cut_frame:
end_cut_frame = s.frame_final_end
if s.frame_final_start > frame and distance_to_start < distance_to_end_cut_frame:
end_cut_frame = s.frame_final_start
return start_cut_frame, end_cut_frame
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors

This file was deleted.

This file was deleted.

@@ -1,8 +1,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
"""
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
@@ -1,8 +1,11 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
"""Drawing utilities. A list of functions to draw common elements"""
# import bgl
import blf
from gpu_extras.batch import batch_for_shader
from mathutils import Vector
import math


def get_color_gizmo_primary(context):
@@ -53,16 +56,24 @@ def draw_rectangle(shader, origin, size, color=(1.0, 1.0, 1.0, 1.0)):
batch.draw(shader)


def draw_triangle(point_1, point_2, point_3, color=(1.0, 1.0, 1.0, 1.0)):
def draw_triangle(shader, point_1, point_2, point_3, color=(1.0, 1.0, 1.0, 1.0)):
vertices = (point_1, point_2, point_3)
indices = ((0, 1, 2),)
shader = gpu.shader.from_builtin("2D_UNIFORM_COLOR")
batch = batch_for_shader(shader, "TRIS", {"pos": vertices}, indices=indices)
shader.bind()
shader.uniform_float("color", color)
batch.draw(shader)


def draw_triangle_equilateral(shader, center, radius, rotation=0.0, color=(1.0, 1.0, 1.0, 1.0)):
points = []
for i in range(3):
angle = i * math.pi * 2 / 3 + rotation
offset = Vector((radius * math.cos(angle), radius * math.sin(angle)))
points.append(center + offset)
draw_triangle(shader, *points, color)


def draw_text(x, y, size, text, justify="left", color=(1.0, 1.0, 1.0, 1.0)):
font_id = 0
blf.color(font_id, *color)

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.