Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Lovato <nathan@gdquest.com>2021-01-24 03:17:35 +0300
committerNathan Lovato <nathan@gdquest.com>2021-01-24 03:17:35 +0300
commitcd176b2617bd3ede969c3aa218ee54a79fc69f27 (patch)
treeb4f51b2c46a95f1ebb8a001269cd8ae6f417bf2a /power_sequencer
parentb482ca0078d5640306985f478bb1dec3dbe7219b (diff)
Update power sequencer to v2.0.1
Changelog: https://github.com/GDQuest/blender-power-sequencer/blob/master/CHANGELOG.md#power-sequencer-201 Commit range: https://github.com/GDQuest/blender-power-sequencer/compare/1.5.0...2.0.1
Diffstat (limited to 'power_sequencer')
-rwxr-xr-xpower_sequencer/__init__.py10
-rw-r--r--power_sequencer/addon_preferences.py2
-rwxr-xr-xpower_sequencer/operators/__init__.py12
-rw-r--r--power_sequencer/operators/channel_offset.py95
-rw-r--r--power_sequencer/operators/cut_strips_under_cursor.py13
-rw-r--r--power_sequencer/operators/delete_direct.py13
-rw-r--r--power_sequencer/operators/expand_to_surrounding_cuts.py35
-rw-r--r--power_sequencer/operators/fade_add.py49
-rw-r--r--power_sequencer/operators/fade_clear.py10
-rw-r--r--power_sequencer/operators/gap_remove.py16
-rw-r--r--power_sequencer/operators/make_hold_frame.py8
-rw-r--r--power_sequencer/operators/scene_create_from_selection.py7
-rw-r--r--power_sequencer/operators/scene_merge_from.py4
-rw-r--r--power_sequencer/operators/select_all_left_or_right.py2
-rw-r--r--power_sequencer/operators/snap.py6
-rw-r--r--power_sequencer/operators/snap_selection.py14
-rw-r--r--power_sequencer/operators/trim_left_or_right_handles.py11
-rw-r--r--power_sequencer/operators/utils/functions.py149
-rw-r--r--power_sequencer/operators/value_offset.py94
-rw-r--r--power_sequencer/scripts/BPSProxy/bpsproxy/__init__.py16
-rw-r--r--power_sequencer/scripts/BPSProxy/bpsproxy/__main__.py171
-rw-r--r--power_sequencer/scripts/BPSProxy/bpsproxy/call.py95
-rw-r--r--power_sequencer/scripts/BPSProxy/bpsproxy/commands.py190
-rw-r--r--power_sequencer/scripts/BPSProxy/bpsproxy/config.py41
-rw-r--r--power_sequencer/scripts/BPSProxy/bpsproxy/utils.py109
-rw-r--r--power_sequencer/scripts/BPSProxy/setup.py57
-rw-r--r--power_sequencer/scripts/BPSRender/bpsrender/__init__.py16
-rw-r--r--power_sequencer/scripts/BPSRender/bpsrender/__main__.py147
-rw-r--r--power_sequencer/scripts/BPSRender/bpsrender/bscripts/mixdown.py30
-rw-r--r--power_sequencer/scripts/BPSRender/bpsrender/bscripts/probe.py30
-rw-r--r--power_sequencer/scripts/BPSRender/bpsrender/bscripts/video.py19
-rw-r--r--power_sequencer/scripts/BPSRender/bpsrender/calls.py410
-rw-r--r--power_sequencer/scripts/BPSRender/bpsrender/commands.py341
-rw-r--r--power_sequencer/scripts/BPSRender/bpsrender/config.py37
-rw-r--r--power_sequencer/scripts/BPSRender/bpsrender/helpers.py110
-rw-r--r--power_sequencer/scripts/BPSRender/bpsrender/setup.py182
-rw-r--r--power_sequencer/scripts/BPSRender/setup.py56
37 files changed, 276 insertions, 2331 deletions
diff --git a/power_sequencer/__init__.py b/power_sequencer/__init__.py
index 3cb5d4f2..8d2c84db 100755
--- a/power_sequencer/__init__.py
+++ b/power_sequencer/__init__.py
@@ -38,11 +38,11 @@ bl_info = {
"name": "Power Sequencer",
"description": "Video editing tools for content creators",
"author": "Nathan Lovato",
- "version": (1, 5, 1),
- "blender": (2, 90, 1),
+ "version": (1, 5, 0),
+ "blender": (2, 81, 0),
"location": "Sequencer",
"tracker_url": "https://github.com/GDquest/Blender-power-sequencer/issues",
- "doc_url": "https://www.gdquest.com/docs/documentation/power-sequencer/",
+ "wiki_url": "https://www.gdquest.com/docs/documentation/power-sequencer/",
"support": "COMMUNITY",
"category": "Sequencer",
}
@@ -80,7 +80,7 @@ def register():
keymaps = register_shortcuts(classes_operator)
addon_keymaps += keymaps
- # print("Registered {} with {} modules".format(bl_info["name"], len(modules)))
+ print("Registered {} with {} modules".format(bl_info["name"], len(modules)))
def unregister():
@@ -104,4 +104,4 @@ def unregister():
unregister_properties()
unregister_handlers()
- # print("Unregistered {}".format(bl_info["name"]))
+ print("Unregistered {}".format(bl_info["name"]))
diff --git a/power_sequencer/addon_preferences.py b/power_sequencer/addon_preferences.py
index 0ca31330..014f63dd 100644
--- a/power_sequencer/addon_preferences.py
+++ b/power_sequencer/addon_preferences.py
@@ -55,7 +55,7 @@ class PowerSequencerPreferences(bpy.types.AddonPreferences):
error_message, info = "", ""
try:
info: str = subprocess.check_output([path, "-version"]).decode("utf-8")
- info = info[:info.find("Copyright")]
+ info = info[: info.find("Copyright")]
print(info)
except (OSError, subprocess.CalledProcessError):
error_message = "Path `{}` is not a valid ffmpeg executable".format(path)
diff --git a/power_sequencer/operators/__init__.py b/power_sequencer/operators/__init__.py
index 064ba9b3..406c8635 100755
--- a/power_sequencer/operators/__init__.py
+++ b/power_sequencer/operators/__init__.py
@@ -22,17 +22,13 @@ 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__")
+ 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="power_sequencer.operators")
- operator_names = [
- entry for entry in dir(module) if entry.startswith("POWER_SEQUENCER_OT")
- ]
+ 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
@@ -41,9 +37,7 @@ doc = {
"sequencer.refresh_all": {
"name": "Refresh All",
"description": "",
- "shortcuts": [
- ({"type": "R", "value": "PRESS", "shift": True}, {}, "Refresh All")
- ],
+ "shortcuts": [({"type": "R", "value": "PRESS", "shift": True}, {}, "Refresh All")],
"demo": "",
"keymap": "Sequencer",
}
diff --git a/power_sequencer/operators/channel_offset.py b/power_sequencer/operators/channel_offset.py
index 35da1b47..48305c4b 100644
--- a/power_sequencer/operators/channel_offset.py
+++ b/power_sequencer/operators/channel_offset.py
@@ -14,17 +14,12 @@
# 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 operator import attrgetter
-from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
-from .utils.functions import (
- slice_selection,
- get_frame_range,
- get_channel_range,
- trim_strips,
- find_strips_in_range,
-)
+import bpy
+
+from .utils.doc import doc_brief, doc_description, doc_idname, doc_name
+from .utils.functions import find_strips_in_range, move_selection, trim_strips
class POWER_SEQUENCER_OT_channel_offset(bpy.types.Operator):
@@ -39,7 +34,7 @@ class POWER_SEQUENCER_OT_channel_offset(bpy.types.Operator):
"shortcuts": [
(
{"type": "UP_ARROW", "value": "PRESS", "alt": True},
- {"direction": "up"},
+ {"direction": "up", "trim_target_channel": False},
"Move to Open Channel Above",
),
(
@@ -49,7 +44,7 @@ class POWER_SEQUENCER_OT_channel_offset(bpy.types.Operator):
),
(
{"type": "DOWN_ARROW", "value": "PRESS", "alt": True},
- {"direction": "down"},
+ {"direction": "down", "trim_target_channel": False},
"Move to Open Channel Below",
),
(
@@ -79,35 +74,71 @@ class POWER_SEQUENCER_OT_channel_offset(bpy.types.Operator):
description="Trim strips to make space in the target channel",
default=False,
)
+ keep_selection_offset: bpy.props.BoolProperty(
+ name="Keep selection offset",
+ description="The selected strips preserve their relative positions",
+ default=True,
+ )
@classmethod
def poll(cls, context):
return context.selected_sequences
def execute(self, context):
+
+ max_channel = 32
+ min_channel = 1
+
+ if self.direction == "up":
+ channel_offset = +1
+ limit_channel = max_channel
+ comparison_function = min
+
+ if self.direction == "down":
+ channel_offset = -1
+ limit_channel = min_channel
+ comparison_function = max
+
selection = [s for s in context.selected_sequences if not s.lock]
+
if not selection:
return {"FINISHED"}
- selection_blocks = slice_selection(context, selection)
- for block in selection_blocks:
- sequences = sorted(block, key=attrgetter("channel", "frame_final_start"))
- frame_start, frame_end = get_frame_range(sequences)
- channel_start, channel_end = get_channel_range(sequences)
-
- if self.trim_target_channel:
- to_delete, to_trim = find_strips_in_range(frame_start, frame_end, context.sequences)
- channel_trim = (
- channel_end + 1 if self.direction == "up" else max(1, channel_start - 1)
- )
- to_trim = [s for s in to_trim if s.channel == channel_trim]
- to_delete = [s for s in to_delete if s.channel == channel_trim]
- trim_strips(context, frame_start, frame_end, to_trim, to_delete)
-
- if self.direction == "up":
- for s in reversed(sequences):
- s.channel += 1
- elif self.direction == "down":
- for s in sequences:
- s.channel = max(1, s.channel - 1)
+ sequences = sorted(selection, key=attrgetter("channel", "frame_final_start"))
+ if self.direction == "up":
+ sequences = [s for s in reversed(sequences)]
+
+ head = sequences[0]
+ if not self.keep_selection_offset or (
+ head.channel != limit_channel and self.keep_selection_offset
+ ):
+ for s in sequences:
+ if self.trim_target_channel:
+ channel_trim = s.channel + channel_offset
+ strips_in_trim_channel = [
+ sequence
+ for sequence in context.sequences
+ if (sequence.channel == channel_trim)
+ ]
+ if strips_in_trim_channel:
+ to_delete, to_trim = find_strips_in_range(
+ s.frame_final_start, s.frame_final_end, strips_in_trim_channel
+ )
+ trim_strips(
+ context, s.frame_final_start, s.frame_final_end, to_trim, to_delete
+ )
+
+ if not self.keep_selection_offset:
+ s.channel = comparison_function(limit_channel, s.channel + channel_offset)
+ if s.channel == limit_channel:
+ move_selection(context, [s], 0, 0)
+
+ if self.keep_selection_offset:
+ start_frame = head.frame_final_start
+ x_difference = 0
+ while not head.channel == limit_channel:
+ move_selection(context, sequences, -x_difference, channel_offset)
+ x_difference = head.frame_final_start - start_frame
+ if x_difference == 0:
+ break
return {"FINISHED"}
diff --git a/power_sequencer/operators/cut_strips_under_cursor.py b/power_sequencer/operators/cut_strips_under_cursor.py
index d7e1141a..3b54a17b 100644
--- a/power_sequencer/operators/cut_strips_under_cursor.py
+++ b/power_sequencer/operators/cut_strips_under_cursor.py
@@ -30,9 +30,7 @@ class POWER_SEQUENCER_OT_split_strips_under_cursor(bpy.types.Operator):
"name": doc_name(__qualname__),
"demo": "https://i.imgur.com/ZyEd0jD.gif",
"description": doc_description(__doc__),
- "shortcuts": [
- ({"type": "K", "value": "PRESS"}, {}, "Cut All Strips Under Cursor")
- ],
+ "shortcuts": [({"type": "K", "value": "PRESS"}, {}, "Cut All Strips Under Cursor")],
"keymap": "Sequencer",
}
bl_idname = doc_idname(__qualname__)
@@ -66,10 +64,5 @@ class POWER_SEQUENCER_OT_split_strips_under_cursor(bpy.types.Operator):
deselect = False
if deselect:
bpy.ops.sequencer.select_all(action="DESELECT")
- (
- context.selected_sequences
- or bpy.ops.power_sequencer.select_strips_under_cursor()
- )
- return bpy.ops.sequencer.split(
- frame=context.scene.frame_current, side=self.side
- )
+ (context.selected_sequences or bpy.ops.power_sequencer.select_strips_under_cursor())
+ return bpy.ops.sequencer.split(frame=context.scene.frame_current, side=self.side)
diff --git a/power_sequencer/operators/delete_direct.py b/power_sequencer/operators/delete_direct.py
index b39ae12f..4c6e4d2d 100644
--- a/power_sequencer/operators/delete_direct.py
+++ b/power_sequencer/operators/delete_direct.py
@@ -50,9 +50,7 @@ class POWER_SEQUENCER_OT_delete_direct(bpy.types.Operator):
bl_description = doc_brief(doc["description"])
bl_options = {"REGISTER", "UNDO"}
- is_removing_transitions: bpy.props.BoolProperty(
- name="Remove Transitions", default=False
- )
+ is_removing_transitions: bpy.props.BoolProperty(name="Remove Transitions", default=False)
@classmethod
def poll(cls, context):
@@ -61,17 +59,12 @@ class POWER_SEQUENCER_OT_delete_direct(bpy.types.Operator):
def invoke(self, context, event):
frame, channel = get_mouse_frame_and_channel(context, event)
if not context.selected_sequences:
- bpy.ops.power_sequencer.select_closest_to_mouse(
- frame=frame, channel=channel
- )
+ bpy.ops.power_sequencer.select_closest_to_mouse(frame=frame, channel=channel)
return self.execute(context)
def execute(self, context):
selection = context.selected_sequences
- if (
- self.is_removing_transitions
- and bpy.ops.power_sequencer.transitions_remove.poll()
- ):
+ if self.is_removing_transitions and bpy.ops.power_sequencer.transitions_remove.poll():
bpy.ops.power_sequencer.transitions_remove()
bpy.ops.sequencer.delete()
diff --git a/power_sequencer/operators/expand_to_surrounding_cuts.py b/power_sequencer/operators/expand_to_surrounding_cuts.py
index 1435b23c..53de8bf7 100644
--- a/power_sequencer/operators/expand_to_surrounding_cuts.py
+++ b/power_sequencer/operators/expand_to_surrounding_cuts.py
@@ -33,11 +33,7 @@ class POWER_SEQUENCER_OT_expand_to_surrounding_cuts(bpy.types.Operator):
"demo": "",
"description": doc_description(__doc__),
"shortcuts": [
- (
- {"type": "E", "value": "PRESS", "ctrl": True},
- {},
- "Expand to Surrounding Cuts",
- )
+ ({"type": "E", "value": "PRESS", "ctrl": True}, {}, "Expand to Surrounding Cuts",)
],
"keymap": "Sequencer",
}
@@ -68,37 +64,24 @@ class POWER_SEQUENCER_OT_expand_to_surrounding_cuts(bpy.types.Operator):
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
+ 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
- ]
- to_extend_right = [
- s for s in sequences if s.frame_final_end == sequences_frame_end
- ]
+ to_extend_left = [s for s in sequences if s.frame_final_start == sequences_frame_start]
+ to_extend_right = [s for s in sequences if s.frame_final_end == sequences_frame_end]
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"}
@@ -110,8 +93,6 @@ def find_closest_cuts(context, frame_min, frame_max):
).frame_final_end
frame_right = min(
context.sequences,
- key=lambda s: s.frame_final_start
- if s.frame_final_start >= frame_max
- else 1000000,
+ key=lambda s: s.frame_final_start if s.frame_final_start >= frame_max else 1000000,
).frame_final_start
return frame_left, frame_right
diff --git a/power_sequencer/operators/fade_add.py b/power_sequencer/operators/fade_add.py
index 1f4d0bfa..dd75c864 100644
--- a/power_sequencer/operators/fade_add.py
+++ b/power_sequencer/operators/fade_add.py
@@ -51,10 +51,7 @@ class POWER_SEQUENCER_OT_fade_add(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
duration_seconds: bpy.props.FloatProperty(
- name="Fade Duration",
- description="Duration of the fade in seconds",
- default=1.0,
- min=0.01,
+ name="Fade Duration", description="Duration of the fade in seconds", default=1.0, min=0.01,
)
type: bpy.props.EnumProperty(
items=[
@@ -98,12 +95,8 @@ class POWER_SEQUENCER_OT_fade_add(bpy.types.Operator):
if s.frame_final_start < context.scene.frame_current < s.frame_final_end
]
- max_duration = min(
- sequences, key=lambda s: s.frame_final_duration
- ).frame_final_duration
- max_duration = (
- floor(max_duration / 2.0) if self.type == "IN_OUT" else max_duration
- )
+ max_duration = min(sequences, key=lambda s: s.frame_final_duration).frame_final_duration
+ max_duration = floor(max_duration / 2.0) if self.type == "IN_OUT" else max_duration
faded_sequences = []
for sequence in sequences:
@@ -113,15 +106,9 @@ class POWER_SEQUENCER_OT_fade_add(bpy.types.Operator):
if not self.is_long_enough(sequence, duration):
continue
- animated_property = (
- "volume" if hasattr(sequence, "volume") else "blend_alpha"
- )
- fade_fcurve = fade_find_or_create_fcurve(
- context, sequence, animated_property
- )
- fades = self.calculate_fades(
- sequence, fade_fcurve, animated_property, duration
- )
+ animated_property = "volume" if hasattr(sequence, "volume") else "blend_alpha"
+ fade_fcurve = fade_find_or_create_fcurve(context, sequence, animated_property)
+ fades = self.calculate_fades(sequence, fade_fcurve, animated_property, duration)
fade_animation_clear(context, fade_fcurve, fades)
fade_animation_create(fade_fcurve, fades)
faded_sequences.append(sequence)
@@ -129,9 +116,7 @@ class POWER_SEQUENCER_OT_fade_add(bpy.types.Operator):
sequence_string = "sequence" if len(faded_sequences) == 1 else "sequences"
self.report(
{"INFO"},
- "Added fade animation to {} {}.".format(
- len(faded_sequences), sequence_string
- ),
+ "Added fade animation to {} {}.".format(len(faded_sequences), sequence_string),
)
return {"FINISHED"}
@@ -232,13 +217,9 @@ class Fade:
if type == "IN":
self.start = Vector((sequence.frame_final_start, 0.0))
- self.end = Vector(
- (sequence.frame_final_start + self.duration, self.max_value)
- )
+ self.end = Vector((sequence.frame_final_start + self.duration, self.max_value))
elif type == "OUT":
- self.start = Vector(
- (sequence.frame_final_end - self.duration, self.max_value)
- )
+ self.start = Vector((sequence.frame_final_end - self.duration, self.max_value))
self.end = Vector((sequence.frame_final_end, 0.0))
def calculate_max_value(self, sequence, fade_fcurve):
@@ -253,15 +234,11 @@ class Fade:
else:
if self.type == "IN":
fade_end = sequence.frame_final_start + self.duration
- keyframes = (
- k for k in fade_fcurve.keyframe_points if k.co[0] >= fade_end
- )
+ keyframes = (k for k in fade_fcurve.keyframe_points if k.co[0] >= fade_end)
if self.type == "OUT":
fade_start = sequence.frame_final_end - self.duration
keyframes = (
- k
- for k in reversed(fade_fcurve.keyframe_points)
- if k.co[0] <= fade_start
+ k for k in reversed(fade_fcurve.keyframe_points) if k.co[0] <= fade_start
)
try:
max_value = next(keyframes).co[1]
@@ -275,6 +252,4 @@ class Fade:
def calculate_duration_frames(context, duration_seconds):
- return round(
- duration_seconds * context.scene.render.fps / context.scene.render.fps_base
- )
+ return round(duration_seconds * context.scene.render.fps / context.scene.render.fps_base)
diff --git a/power_sequencer/operators/fade_clear.py b/power_sequencer/operators/fade_clear.py
index 06b320b0..02516633 100644
--- a/power_sequencer/operators/fade_clear.py
+++ b/power_sequencer/operators/fade_clear.py
@@ -32,11 +32,7 @@ class POWER_SEQUENCER_OT_fade_clear(bpy.types.Operator):
"demo": "",
"description": doc_description(__doc__),
"shortcuts": [
- (
- {"type": "F", "value": "PRESS", "alt": True, "ctrl": True},
- {},
- "Clear Fades",
- )
+ ({"type": "F", "value": "PRESS", "alt": True, "ctrl": True}, {}, "Clear Fades",)
],
"keymap": "Sequencer",
}
@@ -53,9 +49,7 @@ class POWER_SEQUENCER_OT_fade_clear(bpy.types.Operator):
fcurves = context.scene.animation_data.action.fcurves
for sequence in context.selected_sequences:
- animated_property = (
- "volume" if hasattr(sequence, "volume") else "blend_alpha"
- )
+ animated_property = "volume" if hasattr(sequence, "volume") else "blend_alpha"
data_path = sequence.path_from_id() + "." + animated_property
fcurve_map = {
curve.data_path: curve
diff --git a/power_sequencer/operators/gap_remove.py b/power_sequencer/operators/gap_remove.py
index 4a736051..825bfc0d 100644
--- a/power_sequencer/operators/gap_remove.py
+++ b/power_sequencer/operators/gap_remove.py
@@ -71,9 +71,7 @@ class POWER_SEQUENCER_OT_gap_remove(bpy.types.Operator):
else context.sequences
)
sequences = [
- s
- for s in sequences
- if s.frame_final_start >= frame or s.frame_final_end > frame
+ s for s in sequences if s.frame_final_start >= frame or s.frame_final_end > frame
]
sequence_blocks = slice_selection(context, sequences)
if not sequence_blocks:
@@ -100,18 +98,12 @@ class POWER_SEQUENCER_OT_gap_remove(bpy.types.Operator):
Finds and returns the frame at which the gap starts.
Takes a list sequences sorted by frame_final_start.
"""
- strips_start = min(
- sorted_sequences, key=attrgetter("frame_final_start")
- ).frame_final_start
- strips_end = max(
- sorted_sequences, key=attrgetter("frame_final_end")
- ).frame_final_end
+ strips_start = min(sorted_sequences, key=attrgetter("frame_final_start")).frame_final_start
+ strips_end = max(sorted_sequences, key=attrgetter("frame_final_end")).frame_final_end
gap_frame = -1
if strips_start > frame:
- strips_before_frame_start = [
- s for s in context.sequences if s.frame_final_end <= frame
- ]
+ strips_before_frame_start = [s for s in context.sequences if s.frame_final_end <= frame]
frame_target = 0
if strips_before_frame_start:
frame_target = max(
diff --git a/power_sequencer/operators/make_hold_frame.py b/power_sequencer/operators/make_hold_frame.py
index c6893d04..22c2eecf 100644
--- a/power_sequencer/operators/make_hold_frame.py
+++ b/power_sequencer/operators/make_hold_frame.py
@@ -77,9 +77,7 @@ class POWER_SEQUENCER_OT_make_hold_frame(bpy.types.Operator):
try:
next_strip_start = next(
s
- for s in sorted(
- context.sequences, key=operator.attrgetter("frame_final_start")
- )
+ 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
@@ -90,9 +88,7 @@ class POWER_SEQUENCER_OT_make_hold_frame(bpy.types.Operator):
source_blend_type = active.blend_type
sequencer.split(frame=scene.frame_current, type="SOFT", side="RIGHT")
transform.seq_slide(value=(offset, 0))
- sequencer.split(
- 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()
diff --git a/power_sequencer/operators/scene_create_from_selection.py b/power_sequencer/operators/scene_create_from_selection.py
index b615dae9..cd774d0d 100644
--- a/power_sequencer/operators/scene_create_from_selection.py
+++ b/power_sequencer/operators/scene_create_from_selection.py
@@ -67,7 +67,6 @@ class POWER_SEQUENCER_OT_scene_create_from_selection(bpy.types.Operator):
context.window.scene.name = context.selected_sequences[0].name
new_scene_name = context.window.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()
@@ -85,8 +84,10 @@ class POWER_SEQUENCER_OT_scene_create_from_selection(bpy.types.Operator):
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
+ frame_start=selection_start_frame,
+ channel=selection_start_channel,
+ scene=new_scene_name,
)
scene_strip = context.selected_sequences[0]
- # scene_strip.use_sequence = True
+ # scene_strip.use_sequence = True
return {"FINISHED"}
diff --git a/power_sequencer/operators/scene_merge_from.py b/power_sequencer/operators/scene_merge_from.py
index 16b2710d..5c3f40b0 100644
--- a/power_sequencer/operators/scene_merge_from.py
+++ b/power_sequencer/operators/scene_merge_from.py
@@ -73,9 +73,7 @@ class POWER_SEQUENCER_OT_merge_from_scene_strip(bpy.types.Operator):
context.window.scene = strip_scene
bpy.ops.scene.delete()
context.window.scene = start_scene
- self.report(
- type={"WARNING"}, message="Merged scenes lose all their animation data."
- )
+ self.report(type={"WARNING"}, message="Merged scenes lose all their animation data.")
return {"FINISHED"}
diff --git a/power_sequencer/operators/select_all_left_or_right.py b/power_sequencer/operators/select_all_left_or_right.py
index b2e08bb8..01e35306 100644
--- a/power_sequencer/operators/select_all_left_or_right.py
+++ b/power_sequencer/operators/select_all_left_or_right.py
@@ -62,4 +62,4 @@ class POWER_SEQUENCER_OT_select_all_left_or_right(bpy.types.Operator):
return context.sequences
def execute(self, context):
- return bpy.ops.sequencer.select_side_of_frame("INVOKE_DEFAULT", side=self.side)
+ return bpy.ops.sequencer.select("INVOKE_DEFAULT", left_right=self.side)
diff --git a/power_sequencer/operators/snap.py b/power_sequencer/operators/snap.py
index 42b029d1..13d5e66e 100644
--- a/power_sequencer/operators/snap.py
+++ b/power_sequencer/operators/snap.py
@@ -32,11 +32,7 @@ class POWER_SEQUENCER_OT_snap(bpy.types.Operator):
"demo": "",
"description": doc_description(__doc__),
"shortcuts": [
- (
- {"type": "S", "value": "PRESS", "shift": True},
- {},
- "Snap sequences to cursor",
- )
+ ({"type": "S", "value": "PRESS", "shift": True}, {}, "Snap sequences to cursor",)
],
"keymap": "Sequencer",
}
diff --git a/power_sequencer/operators/snap_selection.py b/power_sequencer/operators/snap_selection.py
index 5900579b..eb2851b5 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, apply_time_offset
+from .utils.functions import get_sequences_under_cursor, move_selection
from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
@@ -32,11 +32,7 @@ class POWER_SEQUENCER_OT_snap_selection(bpy.types.Operator):
"demo": "",
"description": doc_description(__doc__),
"shortcuts": [
- (
- {"type": "S", "value": "PRESS", "alt": True},
- {},
- "Snap selection to cursor",
- )
+ ({"type": "S", "value": "PRESS", "alt": True}, {}, "Snap selection to cursor",)
],
"keymap": "Sequencer",
}
@@ -55,9 +51,7 @@ class POWER_SEQUENCER_OT_snap_selection(bpy.types.Operator):
if context.selected_sequences
else get_sequences_under_cursor(context)
)
- frame_first = min(
- sequences, key=lambda s: s.frame_final_start
- ).frame_final_start
+ 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)
+ move_selection(context, sequences, time_offset)
return {"FINISHED"}
diff --git a/power_sequencer/operators/trim_left_or_right_handles.py b/power_sequencer/operators/trim_left_or_right_handles.py
index b93bee53..bbbb8bd5 100644
--- a/power_sequencer/operators/trim_left_or_right_handles.py
+++ b/power_sequencer/operators/trim_left_or_right_handles.py
@@ -78,16 +78,9 @@ class POWER_SEQUENCER_OT_trim_left_or_right_handles(bpy.types.Operator):
frame_current = context.scene.frame_current
# Only select sequences under the time cursor
- sequences = (
- context.selected_sequences
- if context.selected_sequences
- else context.sequences
- )
+ sequences = context.selected_sequences if context.selected_sequences else context.sequences
for s in sequences:
- s.select = (
- s.frame_final_start <= frame_current
- and s.frame_final_end >= frame_current
- )
+ s.select = s.frame_final_start <= frame_current and s.frame_final_end >= frame_current
sequences = [s for s in sequences if s.select]
if not sequences:
return {"FINISHED"}
diff --git a/power_sequencer/operators/utils/functions.py b/power_sequencer/operators/utils/functions.py
index 3462b767..5b33067c 100644
--- a/power_sequencer/operators/utils/functions.py
+++ b/power_sequencer/operators/utils/functions.py
@@ -21,6 +21,9 @@ import bpy
from .global_settings import SequenceTypes
+max_channel = 32
+min_channel = 1
+
def calculate_distance(x1, y1, x2, y2):
return sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
@@ -34,10 +37,8 @@ def find_linked(context, sequences, selected_sequences):
"""
Takes a list of sequences and returns a list of all the sequences
and effects that are linked in time
-
Args:
- - sequences: a list of sequences
-
+ - sequences: a list of sequences
Returns a list of all the linked sequences, but not the sequences passed to the function
"""
start, end = get_frame_range(sequences, selected_sequences)
@@ -99,7 +100,7 @@ def find_sequences_after(context, sequence):
"""
Finds the strips following the sequences passed to the function
Args:
- - Sequences, the sequences to check
+ - Sequences, the sequences to check
Returns all the strips after the sequence in the current context
"""
return [s for s in context.sequences if s.frame_final_start > sequence.frame_final_start]
@@ -133,12 +134,10 @@ def find_snap_candidate(context, frame=0):
def find_strips_mouse(context, frame, channel, select_linked=False):
"""
Finds a list of sequences to select based on the frame and channel the mouse cursor is at
-
Args:
- - frame: the frame the mouse or cursor is on
- - channel: the channel the mouse is hovering
- - select_linked: find and append the sequences linked in time if True
-
+ - frame: the frame the mouse or cursor is on
+ - channel: the channel the mouse is hovering
+ - select_linked: find and append the sequences linked in time if True
Returns the sequence(s) under the mouse cursor as a list
Returns an empty list if nothing found
"""
@@ -201,10 +200,9 @@ def get_mouse_frame_and_channel(context, event):
def is_in_range(context, sequence, start, end):
"""
Checks if a single sequence's start or end is in the range
-
Args:
- - sequence: the sequence to check for
- - start, end: the start and end frames
+ - sequence: the sequence to check for
+ - start, end: the start and end frames
Returns True if the sequence is within the range, False otherwise
"""
s_start = sequence.frame_final_start
@@ -225,47 +223,32 @@ def set_preview_range(context, start, end):
scene.frame_preview_end = end
-def slice_selection(context, sequences):
+def slice_selection(context, sequences, range_block=0):
"""
Takes a list of sequences and breaks it down
into multiple lists of connected sequences
-
Returns a list of lists of sequences,
each list corresponding to a block of sequences
that are connected in time and sorted by frame_final_start
"""
- # Find when 2 sequences are not connected in time
if not sequences:
return []
- break_ids = [0]
+ # Indicates the index number of the lists from the "broken_selection" list
+ index = -1
+ block_end = 0
+ broken_selection = []
sorted_sequences = sorted(sequences, key=attrgetter("frame_final_start"))
- last_sequence = sorted_sequences[0]
- last_biggest_frame_end = last_sequence.frame_final_end
- index = 0
+
for s in sorted_sequences:
- if s.frame_final_start > last_biggest_frame_end + 1:
- break_ids.append(index)
- last_biggest_frame_end = max(last_biggest_frame_end, s.frame_final_end)
- last_sequence = s
- index += 1
-
- # Create lists
- break_ids.append(len(sorted_sequences))
- cuts_count = len(break_ids) - 1
- broken_selection = []
- index = 0
- while index < cuts_count:
- temp_list = []
- index_range = range(break_ids[index], break_ids[index + 1] - 1)
- if len(index_range) == 0:
- temp_list.append(sorted_sequences[break_ids[index]])
- else:
- for counter in range(break_ids[index], break_ids[index + 1]):
- temp_list.append(sorted_sequences[counter])
- if temp_list:
- broken_selection.append(temp_list)
- index += 1
+ if not broken_selection or (block_end + 1 + range_block < s.frame_final_start):
+ broken_selection.append([s])
+ block_end = s.frame_final_end
+ index += 1
+ continue
+ block_end = max(block_end, s.frame_final_end)
+ broken_selection[index].append(s)
+
return broken_selection
@@ -278,8 +261,10 @@ def trim_strips(context, frame_start, frame_end, to_trim, to_delete=[]):
trim_end = max(frame_start, frame_end)
to_trim = [s for s in to_trim if s.type in SequenceTypes.CUTABLE]
+ initial_selection = context.selected_sequences
for s in to_trim:
+ strips_in_target_channel = []
# 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
@@ -290,6 +275,13 @@ def trim_strips(context, frame_start, frame_end, to_trim, to_delete=[]):
bpy.ops.sequencer.split(frame=trim_start, type="SOFT", side="RIGHT")
bpy.ops.sequencer.split(frame=trim_end, type="SOFT", side="LEFT")
to_delete.append(context.selected_sequences[0])
+
+ for c in context.sequences:
+ if c.channel == s.channel:
+ strips_in_target_channel.append(c)
+
+ if s in initial_selection:
+ initial_selection.append(strips_in_target_channel[0])
continue
# Resize strips that overlap the trim range
@@ -298,20 +290,12 @@ def trim_strips(context, frame_start, frame_end, to_trim, to_delete=[]):
elif s.frame_final_end > trim_start and s.frame_final_start < trim_start:
s.frame_final_end = trim_start
- delete_strips(to_delete)
- return {"FINISHED"}
-
-
-def delete_strips(to_delete):
- """
- Deletes the list of sequences `to_delete`
- """
- if not to_delete:
- return
- bpy.ops.sequencer.select_all(action="DESELECT")
for s in to_delete:
+ bpy.context.sequences.remove(s)
+
+ for s in initial_selection:
s.select = True
- bpy.ops.sequencer.delete()
+ return {"FINISHED"}
def find_closest_surrounding_cuts(context, frame):
@@ -378,30 +362,7 @@ def ripple_move(context, sequences, duration_frames, delete=False):
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
+ move_selection(context, to_ripple, duration_frames, 0)
def find_strips_in_range(frame_start, frame_end, sequences, find_overlapping=True):
@@ -409,18 +370,18 @@ def find_strips_in_range(frame_start, frame_end, sequences, find_overlapping=Tru
Returns a tuple of two lists: (strips_inside_range, strips_overlapping_range)
strips_inside_range are strips entirely contained in the frame range.
strips_overlapping_range are strips that only overlap the frame range.
-
Args:
- - frame_start, the start of the frame range
- - frame_end, 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_start, the start of the frame range
+ - frame_end, 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
-
"""
strips_inside_range = []
strips_overlapping_range = []
+ if not sequences:
+ sequences = bpy.context.sequences
for s in sequences:
if (
frame_start <= s.frame_final_start <= frame_end
@@ -429,8 +390,8 @@ def find_strips_in_range(frame_start, frame_end, sequences, find_overlapping=Tru
strips_inside_range.append(s)
elif find_overlapping:
if (
- frame_start <= s.frame_final_end <= frame_end
- or frame_start <= s.frame_final_start <= frame_end
+ frame_start < s.frame_final_end <= frame_end
+ or frame_start <= s.frame_final_start < frame_end
):
strips_overlapping_range.append(s)
@@ -438,3 +399,19 @@ def find_strips_in_range(frame_start, frame_end, sequences, find_overlapping=Tru
if s.frame_final_start < frame_start and s.frame_final_end > frame_end:
strips_overlapping_range.append(s)
return strips_inside_range, strips_overlapping_range
+
+
+def move_selection(context, sequences, frame_offset, channel_offset=0):
+ """Offsets the selected `sequences` horizontally and vertically and preserves
+ the current selected sequences.
+ """
+ if not sequences:
+ return
+ initial_selection = context.selected_sequences
+ bpy.ops.sequencer.select_all(action="DESELECT")
+ for s in sequences:
+ s.select = True
+ bpy.ops.transform.seq_slide(value=(frame_offset, channel_offset))
+ bpy.ops.sequencer.select_all(action="DESELECT")
+ for s in initial_selection:
+ s.select = True
diff --git a/power_sequencer/operators/value_offset.py b/power_sequencer/operators/value_offset.py
new file mode 100644
index 00000000..debd2ef6
--- /dev/null
+++ b/power_sequencer/operators/value_offset.py
@@ -0,0 +1,94 @@
+#
+# 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 .utils.doc import doc_brief, doc_description, doc_idname, doc_name
+from .utils.functions import convert_duration_to_frames
+
+
+class POWER_SEQUENCER_OT_value_offset(bpy.types.Operator):
+ """Instantly offset selected strips, either using frames or seconds. Allows to
+ nudge the selection quickly, using keyboard shortcuts.
+ """
+
+ doc = {
+ "name": doc_name(__qualname__),
+ "demo": "",
+ "description": doc_description(__doc__),
+ "shortcuts": [
+ (
+ {"type": "LEFT_ARROW", "value": "PRESS", "shift": True, "alt": True},
+ {"direction": "left"},
+ "Offset the selection to the left.",
+ ),
+ (
+ {"type": "RIGHT_ARROW", "value": "PRESS", "shift": True, "alt": True},
+ {"direction": "right"},
+ "Offset the selection to the right.",
+ ),
+ ],
+ "keymap": "Sequencer",
+ }
+ bl_idname = doc_idname(__qualname__)
+ bl_label = doc["name"]
+ bl_description = doc_brief(doc["description"])
+ bl_options = {"REGISTER", "UNDO"}
+
+ direction: bpy.props.EnumProperty(
+ items=[
+ ("left", "left", "Move the selection to the left"),
+ ("right", "right", "Move the selection to the right"),
+ ],
+ name="Direction",
+ description="Move the selection given frames or seconds",
+ default="right",
+ options={"HIDDEN"},
+ )
+ value_type: bpy.props.EnumProperty(
+ items=[
+ ("seconds", "Seconds", "Move with the value as seconds"),
+ ("frames", "Frames", "Move with the value as frames"),
+ ],
+ name="Value Type",
+ description="Toggle between offset in frames or seconds",
+ default="seconds",
+ )
+ offset: bpy.props.FloatProperty(
+ name="Offset",
+ description="Offset amount to apply",
+ default=1.0,
+ step=5,
+ precision=3,
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return context.selected_sequences
+
+ def invoke(self, context, event):
+ self.offset = abs(self.offset)
+ if self.direction == "left":
+ self.offset *= -1.0
+ return self.execute(context)
+
+ def execute(self, context):
+ offset_frames = (
+ convert_duration_to_frames(context, self.offset)
+ if self.value_type == "seconds"
+ else self.offset
+ )
+ return bpy.ops.transform.seq_slide(value=(offset_frames, 0))
diff --git a/power_sequencer/scripts/BPSProxy/bpsproxy/__init__.py b/power_sequencer/scripts/BPSProxy/bpsproxy/__init__.py
deleted file mode 100644
index f14cfb6a..00000000
--- a/power_sequencer/scripts/BPSProxy/bpsproxy/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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/>.
-#
diff --git a/power_sequencer/scripts/BPSProxy/bpsproxy/__main__.py b/power_sequencer/scripts/BPSProxy/bpsproxy/__main__.py
deleted file mode 100644
index d8f25204..00000000
--- a/power_sequencer/scripts/BPSProxy/bpsproxy/__main__.py
+++ /dev/null
@@ -1,171 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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/>.
-#
-"""
-Tool to render video proxies using FFMPEG
-Offers mp4 and webm options
-"""
-import argparse as ap
-import glob as g
-import logging as lg
-import os.path as osp
-import sys
-from itertools import compress, starmap, tee
-
-from .call import call, call_makedirs
-from .commands import get_commands, get_commands_vi
-from .config import CONFIG as C
-from .config import LOGGER, LOGLEV
-from .utils import checktools, printw, printd, prints, ToolError
-
-
-def find_files(
- directory=".", ignored_directory=C["proxy_directory"], extensions=C["extensions"]["all"]
-):
- """
- Find files to process.
-
- Parameters
- ----------
- directory: str
- Working directory.
- ignored_directory: str
- Don't check for files in this directory. By default `BL_proxy`.
- extensions: set(str)
- Set of file extensions for filtering the directory tree.
-
- Returns
- -------
- out: list(str)
- List of file paths to be processed.
- """
- if not osp.isdir(directory):
- raise ValueError(("The given path '{}' is not a valid directory.".format(directory)))
- xs = g.iglob("{}/**".format(osp.abspath(directory)), recursive=True)
- xs = filter(lambda x: osp.isfile(x), xs)
- xs = filter(lambda x: ignored_directory not in osp.dirname(x), xs)
- xs = [x for x in xs if osp.splitext(x)[1].lower() in extensions]
- return xs
-
-
-def parse_arguments(cfg):
- """
- Uses `argparse` to parse the command line arguments.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
-
- Returns
- -------
- out: Namespace
- Command line arguments.
- """
- p = ap.ArgumentParser(description="Create proxies for Blender VSE using FFMPEG.")
- p.add_argument(
- "working_directory",
- nargs="?",
- default=".",
- help="The directory containing media to create proxies for",
- )
- p.add_argument(
- "-p",
- "--preset",
- default="mp4",
- choices=cfg["presets"],
- help="a preset name for proxy encoding",
- )
- p.add_argument(
- "-s",
- "--sizes",
- nargs="+",
- type=int,
- default=[25],
- choices=cfg["proxy_sizes"],
- help="A list of sizes of the proxies to render, either 25, 50, or 100",
- )
- p.add_argument(
- "-v", "--verbose", action="count", default=0, help="Increase verbosity level (eg. -vvv)."
- )
- p.add_argument(
- "--dry-run",
- action="store_true",
- help=(
- "Run the script without actual rendering or creating files and"
- " folders. For DEBUGGING purposes"
- ),
- )
-
- clargs = p.parse_args()
- # normalize directory
- clargs.working_directory = osp.abspath(clargs.working_directory)
- # --dry-run implies maximum verbosity level
- clargs.verbose = 99999 if clargs.dry_run else clargs.verbose
- return clargs
-
-
-def main():
- """
- Script entry point.
- """
- tools = ["ffmpeg", "ffprobe"]
- try:
- # get command line arguments and set log level
- clargs = parse_arguments(C)
- lg.basicConfig(level=LOGLEV[min(clargs.verbose, len(LOGLEV) - 1)])
-
- # log basic command line arguments
- clargs.dry_run and LOGGER.info("DRY-RUN")
- LOGGER.info("WORKING-DIRECTORY :: {}".format(clargs.working_directory))
- LOGGER.info("PRESET :: {}".format(clargs.preset))
- LOGGER.info("SIZES :: {}".format(clargs.sizes))
-
- # check for external dependencies
- checktools(tools)
-
- # find files to process
- path_i = find_files(clargs.working_directory)
- kwargs = {"path_i": path_i}
-
- printw(C, "Creating directories if necessary")
- call_makedirs(C, clargs, **kwargs)
-
- printw(C, "Checking for existing proxies")
- cmds = tee(get_commands(C, clargs, what="check", **kwargs))
- stdouts = call(C, clargs, cmds=cmds[0], check=False, shell=True, **kwargs)
- checks = map(lambda s: s.strip().split(), stdouts)
- checks = starmap(lambda fst, *tail: not all(fst == t for t in tail), checks)
- kwargs["path_i"] = list(compress(kwargs["path_i"], checks))
-
- if len(kwargs["path_i"]) != 0:
- printw(C, "Processing", s="\n")
- cmds = get_commands_vi(C, clargs, **kwargs)
- call(C, clargs, cmds=cmds, **kwargs)
- else:
- printd(C, "All proxies exist or no files found, nothing to process", s="\n")
- printd(C, "Done")
- except (ToolError, ValueError) as e:
- LOGGER.error(e)
- prints(C, "Exiting")
- except KeyboardInterrupt:
- prints(C, "DirtyInterrupt. Exiting", s="\n\n")
- sys.exit()
-
-
-# this is so it can be ran as a module: `python3 -m bpsrender` (for testing)
-if __name__ == "__main__":
- main()
diff --git a/power_sequencer/scripts/BPSProxy/bpsproxy/call.py b/power_sequencer/scripts/BPSProxy/bpsproxy/call.py
deleted file mode 100644
index dd74e3a8..00000000
--- a/power_sequencer/scripts/BPSProxy/bpsproxy/call.py
+++ /dev/null
@@ -1,95 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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 multiprocessing as mp
-import os
-import subprocess as sp
-import sys
-
-from functools import partial
-from itertools import chain, tee
-from tqdm import tqdm
-from .config import LOGGER
-from .utils import get_dir, kickstart
-
-WINDOWS = ("win32", "cygwin")
-
-
-def call_makedirs(cfg, clargs, **kwargs):
- """
- Make BL_proxy directories if necessary.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments.
- kwargs: dict
- MANDATORY: path_i
- Dictionary with additional information from previous step.
- """
- path_i = kwargs["path_i"]
- path_d = map(partial(get_dir, cfg, clargs, **kwargs), path_i)
- path_d = tee(chain(*path_d))
- kickstart(map(lambda p: LOGGER.info("Directory @ {}".format(p)), path_d[0]))
- if clargs.dry_run:
- return
- path_d = (os.makedirs(p, exist_ok=True) for p in path_d[1])
- kickstart(path_d)
-
-
-def call(cfg, clargs, *, cmds, **kwargs):
- """
- Generic subprocess calls.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments.
- cmds: iter(tuple(str))
- kwargs: dict
- MANDATORY: path_i
- Dictionary with additional information from previous step.
-
- Returns
- -------
- out: str
- Stdout & Stderr gathered from subprocess call.
- """
- kwargs_s = {
- "stdout": sp.PIPE,
- "stderr": sp.STDOUT,
- "universal_newlines": True,
- "check": kwargs.get("check", True),
- "shell": kwargs.get("shell", False),
- "creationflags": sp.CREATE_NEW_PROCESS_GROUP if sys.platform in WINDOWS else 0,
- }
- if kwargs_s["shell"]:
- cmds = map(lambda cmd: (cmd[0], " ".join(cmd[1])), cmds)
- cmds = tee(cmds)
- kickstart(map(lambda cmd: LOGGER.debug("CALL :: {}".format(cmd[1])), cmds[0]))
- if clargs.dry_run:
- return []
- n = len(kwargs["path_i"])
- ps = tqdm(
- map(lambda cmd: sp.run(cmd[1], **kwargs_s), cmds[1]),
- total=n,
- unit="file" if n == 1 else "files",
- )
- return [p.stdout for p in ps]
diff --git a/power_sequencer/scripts/BPSProxy/bpsproxy/commands.py b/power_sequencer/scripts/BPSProxy/bpsproxy/commands.py
deleted file mode 100644
index 4a28241b..00000000
--- a/power_sequencer/scripts/BPSProxy/bpsproxy/commands.py
+++ /dev/null
@@ -1,190 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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 os.path as osp
-import shlex as sl
-from itertools import chain
-from .utils import get_path
-
-
-def get_commands_check(cfg, clargs, **kwargs):
- """
- ffprobe subprocess command generation.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments.
- cmds: iter(tuple(str))
- kwargs: dict
- MANDATORY: path_i_1, path_o_1
- Dictionary with additional information from previous step.
-
- Returns
- -------
- out: iter(tuple(str))
- Iterator containing commands.
- """
- cmd = (
- "ffprobe -v error -select_streams v:0 -show_entries stream=nb_frames -of"
- " default=noprint_wrappers=1:nokey=1 '{file}'"
- )
- out = map(lambda s: kwargs["path_o_1"].format(size=s), clargs.sizes)
- out = map(lambda f: cmd.format(file=f), out)
- out = sl.split(cmd.format(file=kwargs["path_i_1"]) + " && " + " && ".join(out))
- return iter((out,))
-
-
-def get_commands_image_1(cfg, clargs, **kwargs):
- """
- ffmpeg subprocess command generation for processing an image.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments.
- cmds: iter(tuple(str))
- kwargs: dict
- MANDATORY: path_i_1, path_o_1
- Dictionary with additional information from previous step.
-
- Returns
- -------
- out: iter(tuple(str))
- Iterator containing commands.
- """
- 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(
- lambda s: common.format(size=s[0] / 100.0, path_o_1=s[1]), zip(clargs.sizes, common_all)
- )
- common_all = " ".join(common_all)
- out = sl.split(cmd.format(path_i_1=kwargs["path_i_1"], common_all=common_all))
- return iter((out,))
-
-
-def get_commands_video_1(cfg, clargs, **kwargs):
- """
- ffmpeg subprocess command generation for processing a video.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments.
- cmds: iter(tuple(str))
- kwargs: dict
- MANDATORY: path_i_1, path_o_1
- Dictionary with additional information from previous step.
-
- Returns
- -------
- out: iter(tuple(str))
- Iterator containing commands.
- """
- cmd = "ffmpeg -hwaccel auto -y -v quiet -stats -noautorotate -i '{path_i_1}' {common_all}"
- common = (
- "-pix_fmt yuv420p"
- " -g 1"
- " -sn -an"
- " -vf colormatrix=bt601:bt709"
- " -vf scale=ceil(iw*{size}/2)*2:ceil(ih*{size}/2)*2"
- " {preset}"
- " '{path_o_1}'"
- )
- common_all = map(lambda s: kwargs["path_o_1"].format(size=s), clargs.sizes)
- common_all = map(
- lambda s: common.format(
- preset=cfg["presets"][clargs.preset], size=s[0] / 100.0, path_o_1=s[1]
- ),
- zip(clargs.sizes, common_all),
- )
- common_all = " ".join(common_all)
- out = sl.split(cmd.format(path_i_1=kwargs["path_i_1"], common_all=common_all))
- return iter((out,))
-
-
-def get_commands(cfg, clargs, *, what, **kwargs):
- """
- Delegates the creation of commands lists to appropriate functions based on `what` parameter.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments.
- cmds: iter(tuple(str))
- what: str
- Determines the returned value (see: Returns[out]).
- kwargs: dict
- MANDATORY: path_i
- Dictionary with additional information from previous step.
-
- Returns
- -------
- out: iter(tuple(str, tuple(str)))
- An iterator with the 1st element as a tag (the `what` parameter) and the 2nd
- element as the iterator of the actual commands.
- """
- get_commands_f = {
- "video": get_commands_video_1,
- "image": get_commands_image_1,
- "check": get_commands_check,
- }
- ps = (
- kwargs["path_i"]
- if what not in cfg["extensions"]
- else filter(
- lambda p: osp.splitext(p)[1].lower() in cfg["extensions"][what], kwargs["path_i"]
- )
- )
- ps = map(lambda p: (p, get_path(cfg, clargs, p, **kwargs)), ps)
- out = chain.from_iterable(
- map(lambda p: get_commands_f[what](cfg, clargs, path_i_1=p[0], path_o_1=p[1], **kwargs), ps)
- )
- return map(lambda c: (what, c), out)
-
-
-def get_commands_vi(cfg, clargs, **kwargs):
- """
- Delegates the creation of commands lists to appropriate functions for video/image processing.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments.
- cmds: iter(tuple(str))
- kwargs: dict
- MANDATORY: path_i_1, path_o_1
- Dictionary with additional information from previous step.
-
- Returns
- -------
- out: iter(tuple(str, tuple(str)))
- 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 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
deleted file mode 100644
index 28554e85..00000000
--- a/power_sequencer/scripts/BPSProxy/bpsproxy/config.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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 multiprocessing as mp
-from itertools import chain
-import logging as lg
-
-
-CONFIG = {
- "logger": "BPS",
- "proxy_directory": "BL_proxy",
- "proxy_sizes": (25, 50, 100),
- "extensions": {
- "video": {".mp4", ".mkv", ".mov", ".flv", ".mts"},
- "image": {".png", ".jpg", ".jpeg"},
- },
- "presets": {
- "webm": "-c:v libvpx -crf 25 -speed 16 -threads {}".format(str(mp.cpu_count())),
- "mp4": "-c:v libx264 -crf 25 -preset faster -tune fastdecode",
- "nvenc": "-c:v h264_nvenc -qp 25 -preset fast",
- },
- "pre": {"work": "»", "done": "•", "skip": "~"},
-}
-CONFIG["extensions"]["all"] = set(chain(*CONFIG["extensions"].values()))
-
-LOGGER = lg.getLogger(CONFIG["logger"])
-LOGLEV = [lg.INFO, lg.DEBUG]
-LOGLEV = [None] + sorted(LOGLEV, reverse=True)
diff --git a/power_sequencer/scripts/BPSProxy/bpsproxy/utils.py b/power_sequencer/scripts/BPSProxy/bpsproxy/utils.py
deleted file mode 100644
index 832a0beb..00000000
--- a/power_sequencer/scripts/BPSProxy/bpsproxy/utils.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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/>.
-#
-"""
-Collection of utility functions, class-independent
-"""
-import os.path as osp
-from collections import deque
-from shutil import which
-
-
-class ToolError(Exception):
- """Raised if external dependencies aren't found on system.
- """
-
- pass
-
-
-def checktools(tools):
- tools = [(t, which(t) or "") for t in tools]
- check = {"tools": tools, "test": all(map(lambda x: x[1], tools))}
- if not check["test"]:
- msg = ["BPSProxy couldn't find external dependencies:"]
- msg += [
- "[{check}] {tool}: {path}".format(
- 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"
- " environemnt variable."
- )
- ]
- raise ToolError("\n".join(msg))
-
-
-def get_path_video(cfg, clargs, path, **kwargs):
- return osp.join(
- osp.dirname(path), cfg["proxy_directory"], osp.basename(path), "proxy_{size}.avi"
- )
-
-
-def get_path_image(cfg, clargs, path, **kwargs):
- return osp.join(
- osp.dirname(path),
- cfg["proxy_directory"],
- "images",
- "{size}",
- "{file}_proxy.jpg".format(file=osp.basename(path)),
- )
-
-
-def get_path(cfg, clargs, path, **kwargs):
- get_path_f = {"video": get_path_video, "image": get_path_image}
- what = what_vi(cfg, clargs, path, **kwargs)
- return get_path_f[what](cfg, clargs, path, **kwargs)
-
-
-def get_dir_video(cfg, clargs, path, **kwargs):
- return iter((osp.join(osp.dirname(path), cfg["proxy_directory"], osp.basename(path)),))
-
-
-def get_dir_image(cfg, clargs, path, **kwargs):
- ps = osp.join(osp.dirname(path), cfg["proxy_directory"], "images", "{size}")
- return map(lambda s: ps.format(size=s), clargs.sizes)
-
-
-def get_dir(cfg, clargs, path, **kwargs):
- get_dir_f = {"video": get_dir_video, "image": get_dir_image}
- what = what_vi(cfg, clargs, path, **kwargs)
- return get_dir_f[what](cfg, clargs, path, **kwargs)
-
-
-def what_vi(cfg, clargs, p, **kwargs):
- return "video" if osp.splitext(p)[1].lower() in cfg["extensions"]["video"] else "image"
-
-
-def kickstart(it):
- deque(it, maxlen=0)
-
-
-def printw(cfg, text, s="\n", e="...", p="", **kwargs):
- p = p or cfg["pre"]["work"]
- print("{s}{p} {}{e}".format(text, s=s, e=e, p=p), **kwargs)
-
-
-def printd(cfg, text, s="", e=".", p="", **kwargs):
- p = p or cfg["pre"]["done"]
- printw(cfg, text, s=s, e=e, p=p, **kwargs)
-
-
-def prints(cfg, text, s="", e=".", p="", **kwargs):
- p = p or cfg["pre"]["skip"]
- printw(cfg, text, s=s, e=e, p=p, **kwargs)
diff --git a/power_sequencer/scripts/BPSProxy/setup.py b/power_sequencer/scripts/BPSProxy/setup.py
deleted file mode 100644
index 9676ef60..00000000
--- a/power_sequencer/scripts/BPSProxy/setup.py
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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/>.
-#
-from setuptools import setup
-
-
-def readme():
- with open("README.md") as f:
- return f.read()
-
-
-if __name__ == "__main__":
- setup(
- name="bpsproxy",
- version="0.2.1",
- description="Blender Power Sequencer proxy generator tool",
- long_description=readme(),
- long_description_content_type="text/markdown",
- classifiers=[
- "Development Status :: 5 - Production/Stable",
- "Environment :: Console",
- "Intended Audience :: End Users/Desktop",
- "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
- "Natural Language :: English",
- "Programming Language :: Python :: 3.3",
- "Programming Language :: Python :: 3.4",
- "Programming Language :: Python :: 3.5",
- "Programming Language :: Python :: 3.6",
- "Programming Language :: Python :: 3.7",
- "Programming Language :: Python :: 3",
- "Topic :: Multimedia :: Video",
- "Topic :: Utilities",
- ],
- 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",
- license="GPLv3",
- packages=["bpsproxy"],
- install_requires=["tqdm"],
- zip_safe=False,
- entry_points={"console_scripts": ["bpsproxy=bpsproxy.__main__:main"]},
- include_package_data=True,
- )
diff --git a/power_sequencer/scripts/BPSRender/bpsrender/__init__.py b/power_sequencer/scripts/BPSRender/bpsrender/__init__.py
deleted file mode 100644
index f14cfb6a..00000000
--- a/power_sequencer/scripts/BPSRender/bpsrender/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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/>.
-#
diff --git a/power_sequencer/scripts/BPSRender/bpsrender/__main__.py b/power_sequencer/scripts/BPSRender/bpsrender/__main__.py
deleted file mode 100644
index 07c84fe2..00000000
--- a/power_sequencer/scripts/BPSRender/bpsrender/__main__.py
+++ /dev/null
@@ -1,147 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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/>.
-#
-"""
-Renders videos edited in Blender 3D's Video Sequence Editor using multiple CPU
-cores. Original script by Justin Warren:
-https://github.com/sciactive/pulverize/blob/master/pulverize.py
-Modified by sudopluto (Pranav Sharma), gdquest (Nathan Lovato) and
-razcore (Razvan Radulescu)
-
-Under GPLv3 license
-"""
-import argparse as ap
-import os.path as osp
-import sys
-from functools import partial
-
-from .calls import call
-from .config import CONFIG as C
-from .config import LOGGER
-from .helpers import BSError, ToolError, checktools, kickstart, prints
-from .setup import setup
-
-# https://github.com/mikeycal/the-video-editors-render-script-for-blender#configuring-the-script
-# there seems no easy way to grab the ram usage in a mulitplatform way
-# without writing platform dependent code, or by using a python module
-
-# Most popluar config is 4 cores, 8 GB ram, this is the default for the script
-# https://store.steampowered.com/hwsurvey/
-
-
-def parse_arguments(cfg):
- """
- Uses `argparse` to parse the command line arguments.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
-
- Returns
- -------
- out: Namespace
- Command line arguments (normalized).
- """
- p = ap.ArgumentParser(
- description="Multi-process Blender VSE rendering - will attempt to"
- " create a folder called `render` inside of the folder"
- " containing `blendfile`. Insider `render` another folder called"
- " `parts` will be created for storing temporary files. These files"
- " will be joined together as the last step to produce the final"
- " render which will be stored inside `render` and it will have the"
- " same name as `blendfile`"
- )
- p.add_argument(
- "-o",
- "--output",
- default=".",
- help="Output folder (will contain a `bpsrender` temp folder for" "rendering parts).",
- )
- p.add_argument(
- "-w",
- "--workers",
- type=int,
- default=cfg["cpu_count"],
- help="Number of workers in the pool (for video rendering).",
- )
- p.add_argument(
- "-v", "--verbose", action="count", default=0, help="Increase verbosity level (eg. -vvv)."
- )
- p.add_argument(
- "--dry-run",
- action="store_true",
- help=(
- "Run the script without actual rendering or creating files and"
- " folders. For DEBUGGING purposes"
- ),
- )
- p.add_argument("-s", "--start", type=int, default=None, help="Start frame")
- p.add_argument("-e", "--end", type=int, default=None, help="End frame")
- p.add_argument(
- "-m", "--mixdown-only", action="store_true", help="ONLY render the audio MIXDOWN"
- )
- p.add_argument(
- "-c",
- "--concatenate-only",
- action="store_true",
- help="ONLY CONCATENATE the (already) available video chunks",
- )
- p.add_argument(
- "-d",
- "--video-only",
- action="store_true",
- help="ONLY render the VIDEO (implies --concatenate-only).",
- )
- p.add_argument(
- "-j",
- "--join-only",
- action="store_true",
- help="ONLY JOIN the mixdown with the video. This will produce the" " final render",
- )
- p.add_argument("blendfile", help="Blender project file to render.")
-
- clargs = p.parse_args()
- clargs.blendfile = osp.abspath(clargs.blendfile)
- clargs.output = osp.abspath(clargs.output)
- # --video-only implies --concatenate-only
- clargs.concatenate_only = clargs.concatenate_only or clargs.video_only
- # --dry-run implies maximum verbosity level
- clargs.verbose = 99999 if clargs.dry_run else clargs.verbose
- return clargs
-
-
-def main():
- """
- Script entry point.
- """
- tools = ["blender", "ffmpeg"]
- try:
- clargs = parse_arguments(C)
- checktools(tools)
- cmds, kwargs = setup(C, clargs)
- kickstart(map(partial(call, C, clargs, **kwargs), cmds))
- except (BSError, ToolError) as e:
- LOGGER.error(e)
- except KeyboardInterrupt:
- # TODO: add actual clean up code
- prints(C, "DirtyInterrupt. Exiting", s="\n\n", e="...")
- sys.exit()
-
-
-# this is so it can be ran as a module: `python3 -m bpsrender` (for testing)
-if __name__ == "__main__":
- main()
diff --git a/power_sequencer/scripts/BPSRender/bpsrender/bscripts/mixdown.py b/power_sequencer/scripts/BPSRender/bpsrender/bscripts/mixdown.py
deleted file mode 100644
index b324333a..00000000
--- a/power_sequencer/scripts/BPSRender/bpsrender/bscripts/mixdown.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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
-import os.path as osp
-import sys
-
-if __name__ == "__main__":
- for strip in bpy.context.scene.sequence_editor.sequences_all:
- if strip.type == "META":
- continue
- if strip.type != "SOUND":
- strip.mute = True
-
- path = sys.argv[-1]
- ext = osp.splitext(path)[1][1:].upper()
- bpy.ops.sound.mixdown(filepath=path, check_existing=False, container=ext, codec=ext)
diff --git a/power_sequencer/scripts/BPSRender/bpsrender/bscripts/probe.py b/power_sequencer/scripts/BPSRender/bpsrender/bscripts/probe.py
deleted file mode 100644
index 92cffa60..00000000
--- a/power_sequencer/scripts/BPSRender/bpsrender/bscripts/probe.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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
-
-EXT = {
- "AVI_JPEG": ".avi",
- "AVI_RAW": ".avi",
- "FFMPEG": {"MKV": ".mkv", "OGG": ".ogv", "QUICKTIME": ".mov", "AVI": ".avi", "MPEG4": ".mp4"},
-}
-
-scene = bpy.context.scene
-
-ext = EXT.get(scene.render.image_settings.file_format, "UNDEFINED")
-if scene.render.image_settings.file_format == "FFMPEG":
- ext = ext[scene.render.ffmpeg.format]
-print("\nBPS:{} {} {}\n".format(scene.frame_start, scene.frame_end, ext))
diff --git a/power_sequencer/scripts/BPSRender/bpsrender/bscripts/video.py b/power_sequencer/scripts/BPSRender/bpsrender/bscripts/video.py
deleted file mode 100644
index 590e73eb..00000000
--- a/power_sequencer/scripts/BPSRender/bpsrender/bscripts/video.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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
-
-bpy.context.scene.render.ffmpeg.audio_codec = "NONE"
diff --git a/power_sequencer/scripts/BPSRender/bpsrender/calls.py b/power_sequencer/scripts/BPSRender/bpsrender/calls.py
deleted file mode 100644
index 5a223dd6..00000000
--- a/power_sequencer/scripts/BPSRender/bpsrender/calls.py
+++ /dev/null
@@ -1,410 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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/>.
-#
-# IMPURE
-import multiprocessing as mp
-import os
-import signal as sig
-import subprocess as sp
-from functools import partial, reduce
-from itertools import chain, islice, starmap, tee
-from multiprocessing import Queue
-
-from tqdm import tqdm
-
-from .config import LOGGER
-from .helpers import BSError, checkblender, kickstart, printd, prints, printw
-
-
-def chunk_frames(cfg, clargs, cmds, **kwargs):
- """
- Recover the chunk start/end frames from the constructed commands for the
- video step. This is necessary to preserve purity until later steps.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- cmds: iter(tuple)
- Iterator of commands to be passed to `subprocess`.
- kwargs: dict
- Dictionary with additional information from the setup step.
-
- Returns
- -------
- out: iter(tuple)
- Start/end pairs of frames corresponding to the chunk commands created at
- the video step.
- """
- out = map(lambda x: (x, islice(x, 1, None)), cmds)
- out = map(lambda x: zip(*x), out)
- out = map(lambda x: filter(lambda y: y[0] in ("-s", "-e"), x), out)
- out = map(lambda x: map(lambda y: int(y[1]), x), out)
- out = map(lambda x: reduce(lambda acc, y: acc + (y,), x, ()), out)
- return out
-
-
-def append_chunks_file(cfg, clargs, cmds, **kwargs):
- """
- IMPURE
- Helper function for creating the chunks file that will be used by `ffmpeg`
- to concatenate the chunks into one video file.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- cmds: iter(tuple)
- Iterator of commands to be passed to `subprocess`.
- kwargs: dict
- MANDATORY w_frame_start, w_frame_end, ext
- Dictionary with additional information from the setup step.
- """
- with open(kwargs["chunks_file_path"], "a") as f:
- for fs, fe in chunk_frames(cfg, clargs, cmds, **kwargs):
- f.write(
- "file '{rcp}{fs}-{fe}{ext}'\n".format(
- rcp=kwargs["render_chunk_path"].rstrip("#"),
- fs="{fs:0{frame_pad}d}".format(fs=fs, **cfg),
- fe="{fe:0{frame_pad}d}".format(fe=fe, **cfg),
- **kwargs
- )
- )
-
-
-def call_probe(cfg, clargs, cmds, **kwargs):
- """
- IMPURE
- Probe `clargs.blendfile` for frame start, frame end and extension (for
- video only).
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- cmds: iter(tuple)
- Iterator of commands to be passed to `subprocess`.
- kwargs: dict
- Dictionary with additional information from the setup step.
-
- Returns
- -------
- out: dict
- Dictionary with info extracted from `clargs.blendfile`, namely: start
- frame, end frame and extension (only useful for video step).
- """
- kwargs_p = {"stdout": sp.PIPE, "stderr": sp.STDOUT, "universal_newlines": True}
-
- printw(cfg, "Probing")
- printw(cfg, "Input(blend) @ {}".format(clargs.blendfile), s="")
- frame_start, frame_end, ext = (0, 0, "")
- if not clargs.dry_run:
- with sp.Popen(next(cmds), **kwargs_p) as cp:
- try:
- tmp = map(partial(checkblender, "PROBE", [cfg["probe_py"]], cp), cp.stdout)
- tmp = filter(lambda x: x.startswith("BPS"), tmp)
- tmp = map(lambda x: x[4:].strip().split(), tmp)
- frame_start, frame_end, ext = chain(*tmp)
- except BSError as e:
- LOGGER.error(e)
- except KeyboardInterrupt:
- raise
- finally:
- cp.terminate()
- returncode = cp.poll()
- if returncode != 0:
- raise sp.CalledProcessError(returncode, cp.args)
- frame_start = frame_start if clargs.start is None else clargs.start
- frame_end = frame_end if clargs.end is None else clargs.end
- out = {
- "frame_start": int(frame_start),
- "frame_end": int(frame_end),
- "frames_total": int(frame_end) - int(frame_start) + 1,
- "ext": ext,
- }
- if out["ext"] == "UNDEFINED":
- raise BSError("Video extension is {ext}. Stopping!".format(ext=ext))
- printd(cfg, "Probing done")
- return out
-
-
-def call_mixdown(cfg, clargs, cmds, **kwargs):
- """
- IMPURE
- Calls blender to render the audio mixdown.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- cmds: iter(tuple)
- Iterator of commands to be passed to `subprocess`.
- kwargs: dict
- MANDATORY render_mixdown_path
- Dictionary with additional information from the setup step.
- """
- kwargs_p = {"stdout": sp.PIPE, "stderr": sp.STDOUT, "universal_newlines": True}
-
- printw(cfg, "Rendering mixdown")
- printw(cfg, "Output @ {}".format(kwargs["render_mixdown_path"]), s="")
- if not clargs.dry_run:
- with sp.Popen(next(cmds), **kwargs_p) as cp:
- try:
- tmp = map(partial(checkblender, "MIXDOWN", [cfg["mixdown_py"]], cp), cp.stdout)
- tmp = filter(lambda x: x.startswith("BPS"), tmp)
- tmp = map(lambda x: x[4:].strip().split(), tmp)
- kickstart(tmp)
- except BSError as e:
- LOGGER.error(e)
- except KeyboardInterrupt:
- raise
- finally:
- cp.terminate()
- returncode = cp.poll()
- if returncode != 0:
- raise sp.CalledProcessError(returncode, cp.args)
- printd(cfg, "Mixdown done")
-
-
-def call_chunk(cfg, clargs, queue, cmd, **kwargs):
- """
- IMPURE
- Calls blender to render one chunk (which part is determined by `cmd`).
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- cmd: tuple
- Tuple to be passed to `subprocess`.
- kwargs: dict
- Dictionary with additional information from the setup step.
- """
- sig.signal(sig.SIGINT, sig.SIG_IGN)
- kwargs_p = {"stdout": sp.PIPE, "stderr": sp.STDOUT, "universal_newlines": True}
-
- if not clargs.dry_run:
- # can't use nice functional syntax if we want to simplify with `with`
- with sp.Popen(cmd, **kwargs_p) as cp:
- try:
- tmp = map(
- partial(
- checkblender,
- "VIDEO",
- [cfg["video_py"], "The encoder timebase is not set"],
- cp,
- ),
- cp.stdout,
- )
- tmp = filter(lambda x: x.startswith("Append frame"), tmp)
- tmp = map(lambda x: x.split()[-1], tmp)
- tmp = map(int, tmp)
- tmp = map(lambda x: True, tmp)
- kickstart(map(queue.put, tmp))
- queue.put(False)
- except BSError as e:
- LOGGER.error(e)
-
-
-def call_video(cfg, clargs, cmds, **kwargs):
- """
- IMPURE
- Multi-process call to blender for rendering the (video) chunks.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- cmds: iter(tuple)
- Iterator of commands to be passed to `subprocess`.
- kwargs: dict
- Dictionary with additional information from the setup step.
- """
- printw(cfg, "Rendering video (w/o audio)")
- printw(cfg, "Output @ {}".format(kwargs["render_chunk_path"]), s="")
- try:
- not clargs.dry_run and os.remove(kwargs["chunks_file_path"])
- LOGGER.info("CALL-VIDEO: generating {}".format(kwargs["chunks_file_path"]))
- except OSError as e:
- LOGGER.info("CALL-VIDEO: skipping {}: {}".format(e.filename, e.strerror))
-
- cmds, cmds_cf = tee(cmds)
- (not clargs.dry_run and append_chunks_file(cfg, clargs, cmds_cf, **kwargs))
- # prepare queue/worker
- queues = queues_close = (Queue(),) * clargs.workers
- # prpare processes
- proc = starmap(
- lambda q, cmd: mp.Process(target=partial(call_chunk, cfg, clargs, **kwargs), args=(q, cmd)),
- zip(queues, cmds),
- )
- # split iterator in 2 for later joining the processes and sum
- # one of them
- proc, proc_close = tee(proc)
- proc = map(lambda p: p.start(), proc)
- try:
- not clargs.dry_run and kickstart(proc)
-
- # communicate with processes through the queues and use tqdm to show a
- # simple terminal progress bar baesd on video total frames
- queues = map(lambda q: iter(q.get, False), queues)
- queues = chain(*queues)
- queues = tqdm(queues, total=kwargs["frame_end"] - kwargs["frame_start"] + 1, unit="frames")
- not clargs.dry_run and kickstart(queues)
- except KeyboardInterrupt:
- proc_close = map(lambda x: x.terminate(), proc_close)
- not clargs.dry_run and kickstart(proc_close)
- raise
- finally:
- # close and join processes and queues
- proc_close = map(lambda x: x.join(), proc_close)
- not clargs.dry_run and kickstart(proc_close)
-
- queues_close = map(lambda q: (q, q.close()), queues_close)
- queues_close = starmap(lambda q, _: q.join_thread(), queues_close)
- not clargs.dry_run and kickstart(queues_close)
- printd(cfg, "Video chunks rendering done")
-
-
-def call_concatenate(cfg, clargs, cmds, **kwargs):
- """
- IMPURE
- Calls ffmpeg in order to concatenate the video chunks together.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- cmds: iter(tuple)
- Iterator of commands to be passed to `subprocess`.
- kwargs: dict
- MANDATORY: render_video_path
- Dictionary with additional information from the setup step.
-
- Note
- ----
- It expects the video chunk files to already be available.
- """
- kwargs_p = {"stdout": sp.DEVNULL, "stderr": sp.DEVNULL}
- printw(cfg, "Concatenating (video) chunks")
- printw(cfg, "Output @ {}".format(kwargs["render_video_path"]), s="")
- if not clargs.dry_run:
- with sp.Popen(next(cmds), **kwargs_p) as cp:
- try:
- returncode = cp.wait()
- if returncode != 0:
- raise sp.CalledProcessError(returncode, cp.args)
- except KeyboardInterrupt:
- raise
- finally:
- cp.terminate()
- printd(cfg, "Concatenating done")
-
-
-def call_join(cfg, clargs, cmds, **kwargs):
- """
- IMPURE
- Calls ffmpeg for joining the audio mixdown and the video.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- cmds: iter(tuple)
- Iterator of commands to be passed to `subprocess`.
- kwargs: dict
- MANDATORY: render_audiovideo_path
- Dictionary with additional information from the setup step.
-
- Note
- ----
- It expects the audio mixdown and video files to already be available.
- """
- kwargs_p = {"stdout": sp.DEVNULL, "stderr": sp.DEVNULL}
- printw(cfg, "Joining audio/video")
- printw(cfg, "Output @ {}".format(kwargs["render_audiovideo_path"]), s="")
- if not clargs.dry_run:
- with sp.Popen(next(cmds), **kwargs_p) as cp:
- try:
- returncode = cp.wait()
- if returncode != 0:
- raise sp.CalledProcessError(returncode, cp.args)
- except KeyboardInterrupt:
- raise
- finally:
- cp.terminate()
- printd(cfg, "Joining done")
-
-
-def call(cfg, clargs, cmds, **kwargs):
- """
- IMPURE
- Delegates work to appropriate `call_*` functions.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- cmds: iter(tuple)
- Iterator of commands to be passed to `subprocess`
- kwargs: dict
- MANDATORY: render_audiovideo_path
- Dictionary with additional information from the setup step.
-
- Returns
- -------
- out: dict or None
- It passes on the output from the `call_*` functions. See `call_*` for
- specific details.
-
- Note
- ----
- It tries to be smart and skip steps if child subprocesses give errors.
- Example if `--join-only` is passed, but the audio mixdown or video file
- aren't available on hard drive.
- """
- calls = {
- "probe": call_probe,
- "mixdown": call_mixdown,
- "video": call_video,
- "concatenate": call_concatenate,
- "join": call_join,
- }
- try:
- out = calls[cmds[0]](cfg, clargs, cmds[1], **kwargs)
- return out
- except sp.CalledProcessError:
- prints(
- cfg,
- ("WARNING:{}: Something went wrong when calling" " command - SKIPPING").format(cmds[0]),
- )
diff --git a/power_sequencer/scripts/BPSRender/bpsrender/commands.py b/power_sequencer/scripts/BPSRender/bpsrender/commands.py
deleted file mode 100644
index 910ff021..00000000
--- a/power_sequencer/scripts/BPSRender/bpsrender/commands.py
+++ /dev/null
@@ -1,341 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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 math as m
-from collections import OrderedDict
-from itertools import chain, islice
-
-from .config import LOGGER
-
-
-def get_commands_probe(cfg, clargs, **kwargs):
- """
- Create the command for probing the `clargs.blendfile`.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- kwargs: dict
- Dictionary with additional information from the setup step.
-
- Returns
- -------
- out: iter(list)
- An iterator for which each element is a list to be sent to functions like
- `subprocess.run`.
- """
- out = (
- "blender",
- "--background",
- clargs.blendfile,
- "--python",
- kwargs["probe_py_normalized"],
- "--disable-autoexec",
- )
- LOGGER.debug("CMD-PROBE: {cmd}".format(cmd=" ".join(out)))
- return iter((out,))
-
-
-def get_commands_chunk(cfg, clargs, **kwargs):
- """
- Create the command for rendering a (video) chunk from `clargs.blendfile`.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- kwargs: dict
- MANDATORY render_chunk_path, w_frame_start, w_frame_end
- Dictionary with additional information from the setup step.
-
- Returns
- -------
- out: iter(list)
- An iterator for which each element is a list to be sent to functions like
- `subprocess.run`.
- """
- out = (
- "blender",
- "--background",
- clargs.blendfile,
- "--python",
- kwargs["video_py_normalized"],
- "--disable-autoexec",
- "--render-output",
- kwargs["render_chunk_path"],
- "-s",
- str(kwargs["w_frame_start"]),
- "-e",
- str(kwargs["w_frame_end"]),
- "--render-anim",
- )
- LOGGER.debug(
- "CMD-CHUNK({w_frame_start}-{w_frame_end}): {cmd}".format(cmd=" ".join(out), **kwargs)
- )
- return iter((out,))
-
-
-def get_commands_video(cfg, clargs, **kwargs):
- """
- Create the list of commands (one command per chunk) for rendering a video
- from `clargs.blendfile`.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- kwargs: dict
- MANDATORY chunk_file_path, frame_start, frame_end, frames_total
- Dictionary with additional information from the setup step.
-
- Returns
- -------
- out: iter(tuple)
- An iterator for which each element is a tuple to be sent to functions like
- `subprocess.run`.
- """
- LOGGER.debug("CMD-VIDEO:")
- chunk_length = int(m.floor(kwargs["frames_total"] / clargs.workers))
- out = map(lambda w: (w, kwargs["frame_start"] + w * chunk_length), range(clargs.workers))
- out = map(
- lambda x: (
- x[1],
- x[1] + chunk_length - 1 if x[0] != clargs.workers - 1 else kwargs["frame_end"],
- ),
- out,
- )
- out = map(
- lambda x: get_commands(
- cfg, clargs, "chunk", w_frame_start=x[0], w_frame_end=x[1], **kwargs
- ),
- out,
- )
- out = map(lambda x: x[1], out)
- out = chain(*out)
- return tuple(out)
-
-
-def get_commands_mixdown(cfg, clargs, **kwargs):
- """
- Create the command to render the mixdown from `clargs.blendfile`.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- kwargs: dict
- MANDATORY render_mixdown_path
- Dictionary with additional information from the setup step.
-
- Returns
- -------
- out: iter(tuple)
- An iterator for which each element is a tuple to be sent to functions like
- `subprocess.run`.
- """
- out = (
- "blender --background {blendfile} --python {mixdown_py_normalized}"
- " --disable-autoexec -- {render_mixdown_path}".format(**cfg, **vars(clargs), **kwargs)
- )
- out = (
- "blender",
- "--background",
- clargs.blendfile,
- "--python",
- kwargs["mixdown_py_normalized"],
- "--disable-autoexec",
- "--",
- kwargs["render_mixdown_path"],
- )
- LOGGER.debug("CMD-MIXDOWN: {cmd}".format(cmd=" ".join(out)))
- return iter((out,))
-
-
-def get_commands_concatenate(cfg, clargs, **kwargs):
- """
- Create the command to concatenate the available video chunks generated
- beforehand.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- kwargs: dict
- MANDATORY chunks_file_path, render_video_path
- Dictionary with additional information from the setup step.
-
- Returns
- -------
- out: iter(tuple)
- An iterator for which each element is a tuple to be sent to functions like
- `subprocess.run`.
- """
- out = (
- "ffmpeg",
- "-stats",
- "-f",
- "concat",
- "-safe",
- "-0",
- "-i",
- kwargs["chunks_file_path"],
- "-c",
- "copy",
- "-y",
- kwargs["render_video_path"],
- )
- LOGGER.debug("CMD-CONCATENATE: {cmd}".format(cmd=" ".join(out)))
- return iter((out,))
-
-
-def get_commands_join(cfg, clargs, **kwargs):
- """
- Create the command to join the available audio mixdown and video generated
- beforehand.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- kwargs: dict
- MANDATORY chunks_file_path, render_video_path
- Dictionary with additional information from the setup step.
-
- Returns
- -------
- out: iter(tuple)
- An iterator for which each element is a tuple to be sent to functions like
- `subprocess.run`.
- """
- out = (
- "ffmpeg",
- "-stats",
- "-i",
- kwargs["render_video_path"],
- "-i",
- kwargs["render_mixdown_path"],
- "-map",
- "0:v:0",
- "-c:v",
- "copy",
- "-map",
- "1:a:0",
- "-c:a",
- "aac",
- "-b:a",
- "320k",
- "-y",
- kwargs["render_audiovideo_path"],
- )
- LOGGER.debug("CMD-JOIN: {cmd}".format(cmd=" ".join(out)))
- return iter((out,))
-
-
-def get_commands(cfg, clargs, what="", **kwargs):
- """
- Delegates the creation of commands lists to appropriate functions based on
- `what` parameter.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- what: str (default = '')
- Determines the returned value (see: Returns[out]).
- kwargs: dict
- MANDATORY -- see individual functions for the list of mandatory keys
- Dictionary with additional information from the setup step.
-
- Returns
- -------
- out: iter or (str, iter)
- |- what == '' is True
- An iterator with elements of the type (str) for determining the order in
- which to call the functions in the setup step.
- NOTE: it skipps the "internal use only" functions.
- |- else
- A tuple with the 1st element as a tag (the `what` parameter) and the 2nd
- element as the iterator of the actual commands.
- """
- get_commands_f = OrderedDict(
- (
- # internal use only
- ("probe", get_commands_probe),
- ("chunk", get_commands_chunk),
- # direct connection to command line arguments - in order of execution
- ("mixdown", get_commands_mixdown),
- ("video", get_commands_video),
- ("concatenate", get_commands_concatenate),
- ("join", get_commands_join),
- )
- )
-
- return (
- islice(get_commands_f, 2, None)
- if what == ""
- else (what, get_commands_f[what](cfg, clargs, **kwargs))
- )
-
-
-def get_commands_all(cfg, clargs, **kwargs):
- """
- Prepare the list of commands to be executed depending on the command line
- arguments.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- kwargs: dict
- MANDATORY -- see individual functions for the list of mandatory keys
- Dictionary with additional information from the setup step.
-
- Returns
- -------
- out: iter((str, tuple))
- An iterator for which each element is a (str, iter(tuple)). The string
- value is for tagging the iterator command list (2nd element) for filtering
- later based on the given command line arguments.
- """
- end = "_only"
- out = filter(lambda x: x[0].endswith(end), vars(clargs).items())
- out = map(lambda x: (x[0][: -len(end)], x[1]), out)
- order = list(get_commands(cfg, clargs))
- out = sorted(out, key=lambda x: order.index(x[0]))
- out = (
- map(lambda k: k[0], out)
- if all(map(lambda k: not k[1], out))
- else map(lambda k: k[0], filter(lambda k: k[1], out))
- )
- out = map(lambda k: get_commands(cfg, clargs, k, **kwargs), out)
- return out
diff --git a/power_sequencer/scripts/BPSRender/bpsrender/config.py b/power_sequencer/scripts/BPSRender/bpsrender/config.py
deleted file mode 100644
index b87e3ea3..00000000
--- a/power_sequencer/scripts/BPSRender/bpsrender/config.py
+++ /dev/null
@@ -1,37 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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 logging as lg
-import multiprocessing as mp
-import os.path as osp
-
-CONFIG = {
- "logger": "BPS",
- "cpu_count": min(int(mp.cpu_count() / 2), 6),
- "bs_path": osp.join(osp.dirname(osp.abspath(__file__)), "bscripts"),
- "frame_pad": 7,
- "parts_folder": "bpsrender",
- "chunks_file": "chunks.txt",
- "video_file": "video{}",
- "pre": {"work": "»", "done": "•", "skip": "~"},
- "probe_py": "probe.py",
- "mixdown_py": "mixdown.py",
- "video_py": "video.py",
-}
-
-LOGGER = lg.getLogger(CONFIG["logger"])
-LOGLEV = [lg.INFO, lg.DEBUG]
-LOGLEV = [None] + sorted(LOGLEV, reverse=True)
diff --git a/power_sequencer/scripts/BPSRender/bpsrender/helpers.py b/power_sequencer/scripts/BPSRender/bpsrender/helpers.py
deleted file mode 100644
index 9ebcf2b0..00000000
--- a/power_sequencer/scripts/BPSRender/bpsrender/helpers.py
+++ /dev/null
@@ -1,110 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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/>.
-#
-from collections import deque
-from shutil import which
-
-
-class BSError(Exception):
- """
- Custom Exception raised if Blender is called with a python script argument
- and gives error while trying to execute the script.
- """
-
- pass
-
-
-class ToolError(Exception):
- """Raised if external dependencies aren't found on system.
- """
-
- pass
-
-
-def checktools(tools):
- tools = [(t, which(t) or "") for t in tools]
- check = {"tools": tools, "test": all(map(lambda x: x[1], tools))}
- if not check["test"]:
- msg = ["BPSRender couldn't find external dependencies:"]
- msg += [
- "[{check}] {tool}: {path}".format(
- 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"
- " environemnt variable."
- ),
- "Exiting...",
- ]
- raise ToolError("\n".join(msg))
-
-
-def checkblender(what, search, cp, s):
- """
- IMPURE
- Check Blender output for python script execution error.
-
- Parameters
- ----------
- what: str
- A tag used in the exception message.
- search: iter(str)
- One or more string(s) to search for in Blender's output.
- cp: Popen
- Blender subprocess.
- s: PIPE
- Blender's output.
-
- Returns
- -------
- out: PIPE
- The same pipe `s` is returned so that it can be iterated over on later
- steps.
- """
- if not isinstance(search, list):
- search = [search]
- for search_item in search:
- if search_item in s:
- message = (
- "Script {what} was not properly executed in" " Blender".format(what=what),
- "CMD: {cmd}".format(what=what, cmd=" ".join(cp.args)),
- "DUMP:".format(what=what),
- s,
- )
- raise BSError("\n".join(message))
- return s
-
-
-def printw(cfg, text, s="\n", e="...", p="", **kwargs):
- p = p or cfg["pre"]["work"]
- print("{s}{p} {}{e}".format(text, s=s, e=e, p=p), **kwargs)
-
-
-def printd(cfg, text, s="", e=".", p="", **kwargs):
- p = p or cfg["pre"]["done"]
- printw(cfg, text, s=s, e=e, p=p, **kwargs)
-
-
-def prints(cfg, text, s="", e=".", p="", **kwargs):
- p = p or cfg["pre"]["skip"]
- printw(cfg, text, s=s, e=e, p=p, **kwargs)
-
-
-def kickstart(it):
- deque(it, maxlen=0)
diff --git a/power_sequencer/scripts/BPSRender/bpsrender/setup.py b/power_sequencer/scripts/BPSRender/bpsrender/setup.py
deleted file mode 100644
index aba30d07..00000000
--- a/power_sequencer/scripts/BPSRender/bpsrender/setup.py
+++ /dev/null
@@ -1,182 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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/>.
-#
-# IMPURE
-import logging as lg
-import os
-import os.path as osp
-from functools import reduce
-from itertools import starmap
-
-from .calls import call
-from .commands import get_commands, get_commands_all
-from .config import LOGGER, LOGLEV
-from .helpers import kickstart
-
-
-def setup_bspy(cfg, clargs, **kwargs):
- """
- Normalize the names of the script to be ran in Blender for certain steps.
- Eg. the probe step depends on the script located in
- `bpsrender/cfg['probe_py']`.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- kwargs: dict
-
- Returns
- -------
- out: dict
- Dictoinary to be used in call steps.
- """
- out = filter(lambda x: x[0].endswith("_py"), cfg.items())
- out = starmap(lambda k, v: ("{}_normalized".format(k), osp.join(cfg["bs_path"], v)), out)
- return dict(out)
-
-
-def setup_probe(cfg, clargs, **kwargs):
- """
- IMPURE
- Call Blender and extract information that will be necessary for later
- steps.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- kwargs: dict
- MANDATORY -- see individual functions for the list of mandatory keys
- Dictionary with additional information from the previous setup step.
-
- Returns
- -------
- out: dict
- Dictoinary to be used in call steps.
- """
- return call(cfg, clargs, get_commands(cfg, clargs, "probe", **kwargs), **kwargs)
-
-
-def setup_paths(cfg, clargs, **kwargs):
- """
- Figure out appropriate path locations to store output for parts and final
- render.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- kwargs: dict
- MANDATORY -- see individual functions for the list of mandatory keys
- Dictionary with additional information from the previous setup step.
-
- Returns
- -------
- out: dict
- Dictionary storing all relevant information pertaining to folder and file
- paths.
-
- Note
- ----
- It also creates the folder structure 'render/parts' where
- `clargs.blendfile` is stored on disk.
- """
- render_parts_path = osp.join(clargs.output, cfg["parts_folder"])
- name = osp.splitext(osp.basename(clargs.blendfile))[0]
- render_mixdown_path = osp.join(render_parts_path, "{}_m.flac".format(name))
- render_chunk_path = osp.join(render_parts_path, "{}_c_{}".format(name, "#" * cfg["frame_pad"]))
- render_video_path = osp.join(render_parts_path, "{}_v{}".format(name, kwargs["ext"]))
- render_audiovideo_path = osp.join(clargs.output, "{}{}".format(name, kwargs["ext"]))
- chunks_file_path = osp.join(render_parts_path, cfg["chunks_file"])
-
- out = {
- "render_path": clargs.output,
- "render_parts_path": render_parts_path,
- "chunks_file_path": chunks_file_path,
- "render_chunk_path": render_chunk_path,
- "render_video_path": render_video_path,
- "render_mixdown_path": render_mixdown_path,
- "render_audiovideo_path": render_audiovideo_path,
- }
- return out
-
-
-def setup_folders_hdd(cfg, clargs, **kwargs):
- """
- IMPURE
- Prepares the folder structure `cfg['render']/cfg['parts']'`.
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- kwargs: dict
- Dictionary with additional information from the previous setup step.
-
- Returns
- -------
- out: (iter((str, iter(tuple))), dict)
- 1st element: see commands.py:get_commands_all
- 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
- do_it = filter(lambda x: x[0].endswith("_only"), vars(clargs).items())
- do_it = all(map(lambda x: not x[1], do_it))
- do_it = not clargs.dry_run and clargs.video_only or clargs.mixdown_only or do_it
- do_it and os.makedirs(kwargs["render_parts_path"], exist_ok=True)
- return {}
-
-
-def setup(cfg, clargs):
- """
- IMPURE -- setup_paths
- Prepares the folder structure 'render/parts', the appropriate command lists
- to be called and the keyword arguments to be passed to call functions
- (calls.py).
-
- Parameters
- ----------
- cfg: dict
- Configuration dictionary.
- clargs: Namespace
- Command line arguments (normalized).
- kwargs: dict
- Dictionary with additional information from the previous setup step.
-
- Returns
- -------
- out: (iter((str, iter(tuple))), dict)
- 1st element: see commands.py:get_commands_all
- 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)])
-
- kwargs = dict(reduce(lambda acc, sf: {**acc, **sf(cfg, clargs, **acc)}, setups_f, {}))
-
- LOGGER.info("Setup:")
- kickstart(starmap(lambda k, v: LOGGER.info("{}: {}".format(k, v)), kwargs.items()))
- return get_commands_all(cfg, clargs, **kwargs), kwargs
diff --git a/power_sequencer/scripts/BPSRender/setup.py b/power_sequencer/scripts/BPSRender/setup.py
deleted file mode 100644
index 1f17b9ba..00000000
--- a/power_sequencer/scripts/BPSRender/setup.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#
-# Copyright (C) 2016-2019 by Razvan Radulescu, Nathan Lovato, 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/>.
-#
-from setuptools import setup
-
-
-def readme():
- with open("README.rst") as f:
- return f.read()
-
-
-if __name__ == "__main__":
- setup(
- name="bpsrender",
- version="0.1.40.post1",
- description="Blender Power Sequencer Renderer",
- long_description=readme(),
- classifiers=[
- "Development Status :: 4 - Beta",
- "Environment :: Console",
- "Intended Audience :: End Users/Desktop",
- "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
- "Natural Language :: English",
- "Programming Language :: Python :: 3.3",
- "Programming Language :: Python :: 3.4",
- "Programming Language :: Python :: 3.5",
- "Programming Language :: Python :: 3.6",
- "Programming Language :: Python :: 3.7",
- "Topic :: Text Processing :: Linguistic",
- "Topic :: Multimedia :: Video",
- "Topic :: Utilities",
- ],
- url="https://gitlab.com/razcore/BPSRender",
- keywords="blender render parallel multiprocess speedup utility" " productivty",
- author="Răzvan C. Rădulescu",
- author_email="razcore.art@gmail.com",
- license="GPLv3",
- packages=["bpsrender"],
- install_requires=["tqdm"],
- zip_safe=False,
- entry_points={"console_scripts": ["bpsrender=bpsrender.__main__:main"]},
- include_package_data=True,
- )