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:
authorCampbell Barton <ideasman42@gmail.com>2017-07-24 18:42:10 +0300
committerCampbell Barton <ideasman42@gmail.com>2017-07-24 18:43:02 +0300
commit49230a4c122013bf851ab37b029e7d4f624d68ad (patch)
tree5cbf15367b776c1086376071a16d66604177c998 /sequencer_kinoraw_tools
parentd1b57c76ad5cfd475ad270a93fed2c530fc3e7d0 (diff)
Cleanup: use common prefix for addons
Diffstat (limited to 'sequencer_kinoraw_tools')
-rw-r--r--sequencer_kinoraw_tools/__init__.py399
-rw-r--r--sequencer_kinoraw_tools/audio_tools.py358
-rw-r--r--sequencer_kinoraw_tools/datamosh.py189
-rw-r--r--sequencer_kinoraw_tools/eco.py127
-rw-r--r--sequencer_kinoraw_tools/exiftool.py330
-rw-r--r--sequencer_kinoraw_tools/functions.py455
-rw-r--r--sequencer_kinoraw_tools/jumptocut.py656
-rw-r--r--sequencer_kinoraw_tools/operators_extra_actions.py1302
-rw-r--r--sequencer_kinoraw_tools/proxy_tools.py344
-rw-r--r--sequencer_kinoraw_tools/random_editor.py138
-rw-r--r--sequencer_kinoraw_tools/recursive_loader.py266
-rw-r--r--sequencer_kinoraw_tools/ui.py765
12 files changed, 5329 insertions, 0 deletions
diff --git a/sequencer_kinoraw_tools/__init__.py b/sequencer_kinoraw_tools/__init__.py
new file mode 100644
index 00000000..fe2946f9
--- /dev/null
+++ b/sequencer_kinoraw_tools/__init__.py
@@ -0,0 +1,399 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program 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 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Kinoraw Tools",
+ "author": "Carlos Padial, Turi Scandurra",
+ "version": (0, 5, 2),
+ "blender": (2, 74, 0),
+ "location": "Sequencer",
+ "description": "Compilation of tools to improve video editing with Blender's VSE",
+ "wiki_url": "https://github.com/kinoraw/kinoraw_tools/blob/master/README.md",
+ "tracker_url": "https://github.com/kinoraw/kinoraw_tools",
+ "support": "COMMUNITY",
+ "category": "Sequencer"
+ }
+
+
+if "bpy" in locals():
+ import importlib
+ importlib.reload(jumptocut)
+ importlib.reload(operators_extra_actions)
+ importlib.reload(audio_tools)
+ importlib.reload(proxy_tools)
+ importlib.reload(recursive_loader)
+ importlib.reload(eco)
+ importlib.reload(random_editor)
+ importlib.reload(ui)
+ importlib.reload(datamosh)
+else:
+ from . import jumptocut
+ from . import operators_extra_actions
+ from . import audio_tools
+ from . import proxy_tools
+ from . import recursive_loader
+ from . import eco
+ from . import random_editor
+ from . import ui
+ from . import datamosh
+
+import bpy
+from bpy.types import (
+ AddonPreferences,
+ )
+from bpy.props import (
+ IntProperty,
+ StringProperty,
+ BoolProperty,
+ EnumProperty,
+ )
+
+
+class KinorawToolsAddon(AddonPreferences):
+ # this must match the addon name, use '__package__'
+ # when defining this in a submodule of a python package.
+ bl_idname = __package__
+ bl_option = {'REGISTER'}
+
+ # extra_actions
+ kr_show_tools = BoolProperty(
+ name="Show tools",
+ description="Show extra tools in the panel",
+ default=False
+ )
+ kr_mini_ui = BoolProperty(
+ name="Mini UI",
+ description="Enable mini UI",
+ default=True
+ )
+ kr_show_info = BoolProperty(
+ name="Show info",
+ description="Show basic info from selected strip",
+ default=False
+ )
+ kr_show_trim = BoolProperty(
+ name="Show trim",
+ default=False
+ )
+ kr_show_modifiers = BoolProperty(
+ name="Show modifiers",
+ description="Show modifiers from selected strip",
+ default=False
+ )
+ kr_extra_info = BoolProperty(
+ name="Show extra info",
+ description="Show extra info and settings from selected strip",
+ default=False
+ )
+ # exif
+ use_exif_panel = BoolProperty(
+ name="Exif info Panel | depends on external programs, see Documentation",
+ default=False
+ )
+ # glitch
+ use_glitch_panel = BoolProperty(
+ name="Glitch panel | depends on external programs, see Documentation",
+ default=False
+ )
+ all_keyframes = BoolProperty(
+ name="Remove all keyframes",
+ default=True
+ )
+ load_glitch = BoolProperty(
+ name="Load glitch after conversion > UNSTABLE!!!",
+ default=True
+ )
+ # jump to cut
+ use_jumptocut = BoolProperty(
+ name="Jump to Cut Panel",
+ default=True
+ )
+ use_io_tools = BoolProperty(
+ name="Enable in and out tools in Jump to Cut Panel",
+ default=False
+ )
+ # Proxy Tools
+ use_proxy_tools = BoolProperty(
+ name="Proxy tools Panel | depends on external programs, see Documentation",
+ default=False
+ )
+ proxy_dir = StringProperty(
+ name="Proxy Custom Directory",
+ default="//proxies/"
+ )
+ proxy_scripts_path = StringProperty(
+ name="Directory to store proxy scripts",
+ default="//proxy_scripts/"
+ )
+ proxy_scripts = BoolProperty(
+ name="Generate ffmpeg scripts",
+ default=False
+ )
+ ffmpeg_command = StringProperty(
+ name="Command to generate proxy",
+ default='''ffmpeg -i {} -vcodec mjpeg -q:v 10 -s {}x{} -an -y {}'''
+ )
+ use_internal_proxy = BoolProperty(
+ name="Use internal Blender's proxy system",
+ default=True
+ )
+ use_bi_custom_directory = BoolProperty(
+ name="Proxy Custom Directory",
+ default=True
+ )
+ quality = IntProperty(
+ name="Quality",
+ default=90,
+ min=0, max=32767
+ )
+ tc_list = [
+ ("NONE", "No TC in use", ""), ("RECORD_RUN", "Record Run", ""),
+ ("FREE_RUN", "Free Run", ""), ("FREE_RUN_REC_DATE", "Free Run (rec date)", ""),
+ ("RECORD_RUN_NO_GAPS", "Record Run No Gaps", "")
+ ]
+ timecode = EnumProperty(
+ name="Settings Type",
+ items=tc_list,
+ default="NONE",
+ description="Timecode"
+ )
+ # Audio Tools
+ use_audio_tools = BoolProperty(
+ name="Audio tools Panel | depends on external programs, see Documentation",
+ default=False
+ )
+ audio_dir = StringProperty(
+ name="Path to store extracted audio",
+ default="//audio/"
+ )
+ audio_scripts_path = StringProperty(
+ name="Path to store audio scripts",
+ default="//audio_scripts/"
+ )
+ audio_scripts = BoolProperty(
+ name="Generate ffmpeg scripts",
+ default=False
+ )
+ # Audio Tools - external links
+ audio_use_external_links = BoolProperty(
+ name="Use external audio linked to movie strips",
+ default=False
+ )
+ audio_external_filename = StringProperty(
+ name="File to store info about linked audio",
+ default="//external_audio_sync_info.txt"
+ )
+ # audio tools vu-meter
+ meterbridge = [
+ ("VU", "Classic moving needle VU meter", ""), ("PPM", "PPM meter", ""),
+ ("DPM", "Digital peak meter", ""), ("JF", "'Jellyfish' phase meter", ""),
+ ("SCO", "Oscilloscope meter", "")
+ ]
+ metertype = EnumProperty(
+ name="Meter type",
+ items=meterbridge,
+ default="DPM",
+ description="Meterbridge meter type"
+ )
+ # eco
+ use_eco_tools = BoolProperty(
+ name="Eco tools Panel",
+ default=True
+ )
+ eco_value = IntProperty(
+ name="Number of echoes",
+ default=5,
+ min=1, max=25
+ )
+ eco_offset = IntProperty(
+ name="Echo Offset",
+ default=1,
+ min=-25000, max=25000
+ )
+ eco_use_add_blend_mode = BoolProperty(
+ name='use_add_blend_mode',
+ default=False
+ )
+ # random editor
+ use_random_editor = BoolProperty(
+ name="Random editor Panel | Experimental",
+ default=False
+ )
+ random_frames = IntProperty(
+ name="Frames",
+ default=1,
+ min=1, max=1000
+ )
+ random_selected_scene = StringProperty(
+ name="Selected Scene",
+ default=""
+ )
+ random_use_marker_subsets = BoolProperty(
+ name="Use_Marker subsets",
+ default=True
+ )
+ random_number_of_subsets = IntProperty(
+ name="Number of subsets",
+ default=3,
+ min=1, max=5
+ )
+ show_shortcuts = BoolProperty(
+ name="Hot Keys",
+ default=False,
+ description="List of the shortcuts used for the included various tools",
+ )
+ show_experimental = BoolProperty(
+ name="Tools with External Dependencies / Experimental",
+ default=False,
+ description="List of various tools that need an External Library "
+ "or are Experimental\nPlease read the Documentation "
+ "before enabling them",
+ )
+
+ def draw(self, context):
+ layout = self.layout
+ icon_1 = "TRIA_RIGHT" if not self.show_shortcuts else "TRIA_DOWN"
+ icon_2 = "TRIA_RIGHT" if not self.show_experimental else "TRIA_DOWN"
+
+ box = layout.box()
+ box.prop(self, "use_jumptocut")
+ box = layout.box()
+ box.prop(self, "use_eco_tools")
+
+ box_exp = layout.box()
+ box_exp.prop(self, "show_experimental", emboss=False, icon=icon_2)
+
+ if self.show_experimental:
+ box = box_exp.box()
+ box.prop(self, "use_audio_tools")
+ box = box_exp.box()
+ box.prop(self, "use_proxy_tools")
+ box = box_exp.box()
+ box.prop(self, "use_exif_panel")
+ box = box_exp.box()
+ box.prop(self, "use_glitch_panel")
+ box = box_exp.box()
+ box.prop(self, "use_random_editor")
+
+ box = layout.box()
+ box.prop(self, "show_shortcuts", emboss=False, icon=icon_1)
+
+ if self.show_shortcuts:
+ box.label(text="Skip One Second Forward: Ctrl + Shift + Right Arrow", icon="LAYER_USED")
+ box.label(text="Skip One Second Backwards: Ctrl + Shift + Left Arrow", icon="LAYER_USED")
+ box.label(text="Jump to Previous Strip: Q", icon="LAYER_USED")
+ box.label(text="Jump to Next Strip: W", icon="LAYER_USED")
+ box.label(text="Source IN: Ctrl + Shift + I", icon="LAYER_USED")
+ box.label(text="Source OUT: Ctrl + Shift + O", icon="LAYER_USED")
+ box.label(text="Jump to Previous Marker: Shift + Q", icon="LAYER_USED")
+ box.label(text="Jump to Next Marker: Shift + W", icon="LAYER_USED")
+
+
+# Registration
+def register():
+ bpy.utils.register_class(KinorawToolsAddon)
+
+ bpy.utils.register_module(__name__)
+
+ # Append menu entries
+ bpy.types.SEQUENCER_MT_add.prepend(ui.sequencer_add_menu_func)
+ bpy.types.SEQUENCER_MT_select.prepend(ui.sequencer_select_menu_func)
+ bpy.types.SEQUENCER_MT_strip.prepend(ui.sequencer_strip_menu_func)
+ bpy.types.SEQUENCER_HT_header.append(ui.sequencer_header_func)
+ bpy.types.CLIP_HT_header.append(ui.clip_header_func)
+ bpy.types.CLIP_MT_clip.prepend(ui.clip_clip_menu_func)
+ bpy.types.TIME_MT_frame.prepend(ui.time_frame_menu_func)
+ bpy.types.TIME_HT_header.append(ui.time_header_func)
+
+ # Add keyboard shortcut configuration
+ kc = bpy.context.window_manager.keyconfigs.addon
+ km = kc.keymaps.new(name='Frames')
+
+ # jump 1 second
+ kmi = km.keymap_items.new('screenextra.frame_skip',
+ 'RIGHT_ARROW', 'PRESS', ctrl=True, shift=True)
+ kmi.properties.back = False
+ kmi = km.keymap_items.new('screenextra.frame_skip',
+ 'LEFT_ARROW', 'PRESS', ctrl=True, shift=True)
+ kmi.properties.back = True
+
+ # jump to cut
+ kmi = km.keymap_items.new("sequencer.strip_jump",
+ 'Q', 'PRESS', ctrl=False, shift=False)
+ kmi.properties.next = False
+ kmi.properties.center = False
+ kmi = km.keymap_items.new("sequencer.strip_jump",
+ 'W', 'PRESS', ctrl=False, shift=False)
+ kmi.properties.next = True
+ kmi.properties.center = False
+
+ # in and out
+ kmi = km.keymap_items.new("sequencerextra.sourcein",
+ 'I', 'PRESS', ctrl=True, shift=True)
+ kmi = km.keymap_items.new("sequencerextra.sourceout",
+ 'O', 'PRESS', ctrl=True, shift=True)
+
+ # markers
+ kc = bpy.context.window_manager.keyconfigs.active
+ km = kc.keymaps.new(name='Screen')
+ kmi = km.keymap_items.new("screen.marker_jump",
+ 'Q', 'PRESS', ctrl=False, shift=True)
+ kmi.properties.next = False
+ kmi = km.keymap_items.new("screen.marker_jump",
+ 'W', 'PRESS', ctrl=False, shift=True)
+ kmi.properties.next = True
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
+
+ try:
+ bpy.utils.unregister_class(KinorawToolsAddon)
+ except RuntimeError:
+ pass
+
+ # Remove menu entries
+ bpy.types.SEQUENCER_MT_add.remove(ui.sequencer_add_menu_func)
+ bpy.types.SEQUENCER_MT_select.remove(ui.sequencer_select_menu_func)
+ bpy.types.SEQUENCER_MT_strip.remove(ui.sequencer_strip_menu_func)
+ bpy.types.SEQUENCER_HT_header.remove(ui.sequencer_header_func)
+ bpy.types.CLIP_HT_header.remove(ui.clip_header_func)
+ bpy.types.CLIP_MT_clip.remove(ui.clip_clip_menu_func)
+ bpy.types.TIME_MT_frame.remove(ui.time_frame_menu_func)
+ bpy.types.TIME_HT_header.remove(ui.time_header_func)
+
+ # Remove keyboard shortcut configuration
+ kc = bpy.context.window_manager.keyconfigs.addon
+ km = kc.keymaps['Frames']
+ km.keymap_items.remove(km.keymap_items['screenextra.frame_skip'])
+ km.keymap_items.remove(km.keymap_items['screenextra.frame_skip'])
+
+ km.keymap_items.remove(km.keymap_items['sequencer.strip_jump'])
+ km.keymap_items.remove(km.keymap_items['sequencer.strip_jump'])
+
+ km.keymap_items.remove(km.keymap_items['sequencerextra.sourcein'])
+ km.keymap_items.remove(km.keymap_items['sequencerextra.sourceout'])
+
+ kc = bpy.context.window_manager.keyconfigs.active
+ km = kc.keymaps['Screen']
+ km.keymap_items.remove(km.keymap_items['screen.marker_jump'])
+ km.keymap_items.remove(km.keymap_items['screen.marker_jump'])
+
+
+if __name__ == '__main__':
+ register()
diff --git a/sequencer_kinoraw_tools/audio_tools.py b/sequencer_kinoraw_tools/audio_tools.py
new file mode 100644
index 00000000..bf615d5a
--- /dev/null
+++ b/sequencer_kinoraw_tools/audio_tools.py
@@ -0,0 +1,358 @@
+# gpl: authors Carlos Padial, Turi Scandurra
+
+import bpy
+import os
+from bpy.types import (
+ Operator,
+ Panel,
+ )
+import subprocess
+from . import functions
+
+
+proxy_qualities = [
+ ("1", "25%", ""), ("2", "50%", ""),
+ ("3", "75%", ""), ("4", "100%", "")]
+
+#
+# ls *.sh | parallel -j 8 sh {}
+#
+
+
+# functions
+def createsyncfile(filename):
+ if not os.path.isfile(bpy.path.abspath(filename)):
+ f = open(bpy.path.abspath(filename), "w")
+ data = []
+
+ try:
+ f.writelines(data) # Write a sequence of strings to a file
+ finally:
+ f.close()
+
+
+def readsyncfile(filename):
+ try:
+ file = open(bpy.path.abspath(filename))
+ data = file.readlines()
+ file.close()
+
+ return data
+
+ except IOError:
+ pass
+
+
+def writesyncfile(filename, data):
+ try:
+ f = open(bpy.path.abspath(filename), "w")
+ try:
+ for line in data:
+ f.writelines(line) # Write a sequence of strings to a file
+ finally:
+ f.close()
+
+ except IOError:
+ pass
+
+
+# classes
+
+class ExtractWavOperator(Operator):
+ bl_idname = "sequencer.extract_wav_operator"
+ bl_label = "Extract Wav from movie strip Operator"
+ bl_description = "Use ffmpeg to extract audio from video and import it synced"
+
+ @staticmethod
+ def has_sequencer(context):
+ return (context.space_data.view_type in
+ {'SEQUENCER', 'SEQUENCER_PREVIEW'})
+
+ @classmethod
+ def poll(self, context):
+ strip = functions.act_strip(context)
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return strip.type in ('MOVIE')
+ else:
+ return False
+
+ def execute(self, context):
+
+ preferences = context.user_preferences
+ audio_dir = preferences.addons[__package__].preferences.audio_dir
+
+ functions.create_folder(bpy.path.abspath(audio_dir))
+
+ for strip in context.selected_editable_sequences:
+
+ # get filename
+ if strip.type == "MOVIE":
+ filename = bpy.path.abspath(strip.filepath)
+ newfilename = bpy.path.abspath(strip.filepath).rpartition(
+ "/")[2]
+ fileoutput = os.path.join(
+ bpy.path.abspath(audio_dir),
+ newfilename) + ".wav"
+
+ # check for wav existing file
+ if not os.path.isfile(fileoutput):
+ # if not, extract the file
+ extract_audio = "ffmpeg -i '{}' -acodec pcm_s16le -ac 2 {}".\
+ format(filename, fileoutput)
+ print(extract_audio)
+ os.system(extract_audio)
+ else:
+ print("The audio File exists")
+
+ if strip.type == "MOVIE":
+ # import the file and trim in the same way the original
+ bpy.ops.sequencer.sound_strip_add(
+ filepath=fileoutput,
+ frame_start=strip.frame_start,
+ channel=strip.channel + 1,
+ replace_sel=True, overlap=False,
+ cache=False
+ )
+
+ # Update scene
+ context.scene.update()
+
+ newstrip = context.scene.sequence_editor.active_strip
+
+ # deselect all other strips
+ for i in context.selected_editable_sequences:
+ if i.name != newstrip.name:
+ i.select = False
+
+ # Update scene
+ context.scene.update()
+
+ # Match the original clip's length
+ newstrip.frame_start = strip.frame_start - strip.animation_offset_start
+
+ functions.triminout(newstrip,
+ strip.frame_start + strip.frame_offset_start,
+ strip.frame_start + strip.frame_offset_start +
+ strip.frame_final_duration)
+
+ return {'FINISHED'}
+
+
+class ExternalAudioSetSyncOperator(Operator):
+ bl_idname = "sequencer.external_audio_set_sync"
+ bl_label = "set sync info"
+ bl_description = ("Get sync info from selected audio and video strip "
+ "and store it into a text file")
+
+ @staticmethod
+ def has_sequencer(context):
+ return (context.space_data.view_type in
+ {'SEQUENCER', 'SEQUENCER_PREVIEW'})
+
+ @classmethod
+ def poll(cls, context):
+ if cls.has_sequencer(context):
+ if len(context.selected_editable_sequences) == 2:
+ types = []
+ for i in context.selected_editable_sequences:
+ types.append(i.type)
+ if 'MOVIE' and 'SOUND' in types:
+ return True
+ else:
+ return False
+
+ def execute(self, context):
+
+ preferences = context.user_preferences
+ filename = preferences.addons[__package__].preferences.audio_external_filename
+
+ for strip in context.selected_editable_sequences:
+ if strip.type == "MOVIE":
+ moviestrip = strip
+ elif strip.type == "SOUND":
+ soundstrip = strip
+
+ offset = str(moviestrip.frame_start - soundstrip.frame_start)
+
+ data1 = readsyncfile(filename)
+ data2 = []
+ newline = moviestrip.filepath + " " + soundstrip.filepath + " " + offset + "\n"
+
+ if data1 is not None:
+ repeated = False
+ for line in data1:
+ if line.split()[0] == moviestrip.filepath and line.split()[1] == soundstrip.filepath:
+ data2.append(newline)
+ repeated = True
+ else:
+ data2.append(line)
+ if not repeated:
+ data2.append(newline)
+ else:
+ data2.append(newline)
+
+ createsyncfile(filename)
+ writesyncfile(filename, data2)
+
+ return {'FINISHED'}
+
+
+class ExternalAudioReloadOperator(Operator):
+ bl_idname = "sequencer.external_audio_reload"
+ bl_label = "Reload External audio"
+ bl_description = ("Reload external audio synced to selected movie strip "
+ "acording to info from a text file")
+
+ @staticmethod
+ def has_sequencer(context):
+ return (context.space_data.view_type in
+ {'SEQUENCER', 'SEQUENCER_PREVIEW'})
+
+ @classmethod
+ def poll(cls, context):
+ if cls.has_sequencer(context):
+ if len(context.selected_editable_sequences) == 1:
+ if context.selected_editable_sequences[0].type == 'MOVIE':
+ return True
+ else:
+ return False
+
+ def execute(self, context):
+ preferences = context.user_preferences
+ filename = preferences.addons[__package__].preferences.audio_external_filename
+
+ data = readsyncfile(filename)
+
+ for strip in context.selected_editable_sequences:
+ sounds = []
+
+ for line in data:
+ if line.split()[0] == strip.filepath:
+ moviefile = bpy.path.abspath(line.split()[0])
+ soundfile = bpy.path.abspath(line.split()[1])
+ offset = int(line.split()[2])
+ sounds.append((soundfile, offset))
+
+ for soundfile, offset in sounds:
+ print(soundfile, offset)
+ print(strip.filepath)
+ # find start frame for sound strip (using offset from file)
+ sound_frame_start = strip.frame_start - strip.animation_offset_start - offset
+
+ # import the file and trim in the same way the original
+ bpy.ops.sequencer.sound_strip_add(
+ filepath=soundfile,
+ frame_start=sound_frame_start,
+ channel=strip.channel + 1,
+ replace_sel=True, overlap=False,
+ cache=False
+ )
+
+ # Update scene
+ context.scene.update()
+
+ newstrip = context.scene.sequence_editor.active_strip
+
+ # deselect all other strips
+ for i in context.selected_editable_sequences:
+ if i.name != newstrip.name:
+ i.select = False
+
+ # Update scene
+ context.scene.update()
+
+ # trim sound strip like original one
+ functions.triminout(newstrip,
+ strip.frame_start + strip.frame_offset_start,
+ strip.frame_start + strip.frame_offset_start +
+ strip.frame_final_duration
+ )
+
+ return {'FINISHED'}
+
+
+class AudioToolPanel(Panel):
+ bl_label = "Audio Tools"
+ bl_idname = "OBJECT_PT_AudioTool"
+ bl_space_type = 'SEQUENCE_EDITOR'
+ bl_region_type = 'UI'
+
+ @classmethod
+ def poll(self, context):
+ if context.space_data.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}:
+ scn = context.scene
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ if prefs.use_audio_tools:
+ return True
+ else:
+ return False
+
+ def draw_header(self, context):
+ layout = self.layout
+ layout.label(text="", icon="PLAY_AUDIO")
+
+ def draw(self, context):
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+
+ strip = functions.act_strip(context)
+
+ if strip.type == "MOVIE":
+ layout = self.layout
+ layout.prop(prefs, "audio_dir", text="Path for Audio files")
+
+ layout.operator("sequencer.extract_wav_operator", text="Extract Wav")
+
+ layout = self.layout
+ layout.prop(prefs, "audio_scripts")
+
+ if prefs.audio_scripts:
+ layout = self.layout
+ layout.prop(prefs, "audio_scripts_path", text="Path for scripts")
+
+ layout = self.layout
+ layout.prop(prefs, "audio_use_external_links", text="External Audio sync")
+
+ if prefs.audio_use_external_links:
+ layout = self.layout
+ layout.prop(prefs, "audio_external_filename", text="Sync data")
+
+ row = layout.row(align=True)
+ row.operator("sequencer.external_audio_set_sync", text="Set sync")
+ row.operator("sequencer.external_audio_reload", text="Reload Audio")
+
+ layout = self.layout
+
+ row = layout.row()
+ row.prop(prefs, "metertype", text="")
+ row.operator("sequencer.openmeterbridge",
+ text="Launch Audio Meter", icon="SOUND")
+
+
+class OpenMeterbridgeOperator(Operator):
+ bl_idname = "sequencer.openmeterbridge"
+ bl_label = "External VU meter"
+ bl_description = "Open external VU meter to work with Jack"
+
+ @staticmethod
+ def has_sequencer(context):
+ return (context.space_data.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'})
+
+ @classmethod
+ def poll(cls, context):
+ if cls.has_sequencer(context):
+ if len(context.selected_editable_sequences) == 1:
+ return True
+
+ def execute(self, context):
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+
+ command = "meterbridge -t {} 'PulseAudio JACK Sink:front-left' " \
+ "'PulseAudio JACK Sink:front-right' &".format(prefs.metertype.lower())
+ p = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
+
+ return {'FINISHED'}
diff --git a/sequencer_kinoraw_tools/datamosh.py b/sequencer_kinoraw_tools/datamosh.py
new file mode 100644
index 00000000..be6ea89d
--- /dev/null
+++ b/sequencer_kinoraw_tools/datamosh.py
@@ -0,0 +1,189 @@
+# gpl: authors Carlos Padial, Turi Scandurra
+
+import bpy
+import os
+from bpy.props import IntProperty
+from bpy.types import (
+ Operator,
+ Panel,
+ )
+from . import functions
+
+
+proxy_qualities = [
+ ("1", "25%", ""), ("2", "50%", ""),
+ ("3", "75%", ""), ("4", "100%", ""),
+ ("5", "none", "")
+ ]
+
+
+# functions
+def createdatamosh(context, strip):
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+
+ fileinput = bpy.path.abspath(strip.filepath)
+ fileoutput = fileinput.rpartition(".")[0] + "_datamosh.avi"
+
+ if prefs.all_keyframes:
+ command = "datamosh '{}' -a -o '{}'".format(fileinput, fileoutput)
+ else:
+ command = "datamosh '{}' -o '{}'".format(fileinput, fileoutput)
+ print(command)
+ os.system(command)
+ return fileoutput
+
+
+def createavi(context, strip):
+ fileinput = bpy.path.abspath(strip.filepath)
+ fileoutput = fileinput.rpartition(".")[0] + "_.avi"
+
+ command = "ffmpeg -i '{}' -vcodec copy '{}'".format(fileinput, fileoutput)
+
+ print(command)
+ os.system(command)
+
+ return fileoutput
+
+
+def createavimjpeg(context, strip):
+ fileinput = bpy.path.abspath(strip.filepath)
+ fileoutput = fileinput.rpartition(".")[0] + "_mjpeg.avi"
+
+ command = "ffmpeg -i '{}' -vcodec mjpeg -q:v 1 '{}'".format(fileinput, fileoutput)
+
+ print(command)
+ os.system(command)
+
+ return fileoutput
+
+
+# classes
+class CreateAvi(Operator):
+ bl_idname = "sequencer.createavi"
+ bl_label = "Create avi file"
+ bl_description = "Create an avi output file"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ size = IntProperty(
+ name="proxysize",
+ default=1
+ )
+
+ @classmethod
+ def poll(self, context):
+ strip = functions.act_strip(context)
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return strip.type in ('MOVIE')
+ else:
+ return False
+
+ def execute(self, context):
+ strips = functions.get_selected_strips(context)
+
+ for strip in strips:
+ # deselect all other strips
+ for i in strips:
+ i.select = False
+ # select current strip
+ strip.select = True
+ if strip.type == "MOVIE":
+ if self.size == 1:
+ fileoutput = createavi(context, strip)
+ elif self.size == 2:
+ fileoutput = createavimjpeg(context, strip)
+ strip.filepath = fileoutput
+
+ # select all strips again
+ for strip in strips:
+ try:
+ strip.select = True
+ except ReferenceError:
+ pass
+
+ bpy.ops.sequencer.reload()
+
+ return {'FINISHED'}
+
+
+class CreateDatamosh(Operator):
+ bl_idname = "sequencer.createdatamosh"
+ bl_label = "Create Datamosh"
+ bl_description = "Create Datamosh"
+
+ @classmethod
+ def poll(self, context):
+ strip = functions.act_strip(context)
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return strip.type in ('MOVIE')
+ else:
+ return False
+
+ def execute(self, context):
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+ strips = functions.get_selected_strips(context)
+
+ for strip in strips:
+ # deselect all other strips
+ for i in strips:
+ i.select = False
+ # select current strip
+ strip.select = True
+ if strip.type == "MOVIE":
+ fileoutput = createdatamosh(context, strip)
+ if prefs.load_glitch:
+ strip.filepath = fileoutput
+
+ # select all strips again
+ for strip in strips:
+ try:
+ strip.select = True
+ except ReferenceError:
+ pass
+
+ bpy.ops.sequencer.reload()
+
+ return {'FINISHED'}
+
+
+class CreateGlitchToolPanel(Panel):
+ bl_label = "Glitch Tools"
+ bl_idname = "OBJECT_PT_GlitchTool"
+ bl_space_type = 'SEQUENCE_EDITOR'
+ bl_region_type = 'UI'
+
+ @classmethod
+ def poll(self, context):
+ if context.space_data.view_type in {'SEQUENCER',
+ 'SEQUENCER_PREVIEW'}:
+ strip = functions.act_strip(context)
+ scn = context.scene
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ if prefs.use_glitch_panel:
+ return strip.type in ('MOVIE')
+ else:
+ return False
+
+ def draw_header(self, context):
+ layout = self.layout
+ layout.label(text="", icon="GAME")
+
+ def draw(self, context):
+
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+
+ layout = self.layout
+
+ layout.operator("sequencer.createavi", text="Create avi (same codec)")
+ layout.operator("sequencer.createavi", text="Create avi (mjpeg)").size = 2
+
+ layout.prop(prefs, "all_keyframes")
+ layout.prop(prefs, "load_glitch")
+
+ layout.operator("sequencer.createdatamosh")
diff --git a/sequencer_kinoraw_tools/eco.py b/sequencer_kinoraw_tools/eco.py
new file mode 100644
index 00000000..62382389
--- /dev/null
+++ b/sequencer_kinoraw_tools/eco.py
@@ -0,0 +1,127 @@
+# File sequencer_slide_strip.py
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program 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 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
+import bpy
+from bpy.types import (
+ Operator,
+ Panel,
+ )
+from . import functions
+
+
+class EcoPanel(Panel):
+ bl_label = "Eco Tool"
+ bl_idname = "OBJECT_PT_EcoTool"
+ bl_space_type = "SEQUENCE_EDITOR"
+ bl_region_type = "UI"
+
+ @staticmethod
+ def has_sequencer(context):
+ return (context.space_data.view_type in
+ {'SEQUENCER', 'SEQUENCER_PREVIEW'})
+
+ @classmethod
+ def poll(self, context):
+ if context.space_data.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}:
+ strip = functions.act_strip(context)
+ scn = context.scene
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ if prefs.use_eco_tools:
+ return strip.type in ('META')
+ else:
+ return False
+
+ def draw_header(self, context):
+ layout = self.layout
+ layout.label(text="", icon="FORCE_HARMONIC")
+
+ def draw(self, context):
+ strip = functions.act_strip(context)
+ seq_type = strip.type
+
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+
+ if seq_type in ('MOVIE', 'IMAGE', 'META', 'MOVIECLIP', 'SCENE'):
+ layout = self.layout
+ col = layout.column()
+
+ col.prop(prefs, "eco_value", text="Ecos")
+ col.prop(prefs, "eco_offset", text="Offset")
+ col.prop(prefs, "eco_use_add_blend_mode", text="Use add blend mode")
+ col.operator("sequencer.eco")
+
+
+class OBJECT_OT_EcoOperator(Operator):
+ bl_idname = "sequencer.eco"
+ bl_label = "Eco operator"
+ bl_description = "Generate an echo effect by duplicating the selected strip"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @staticmethod
+ def has_sequencer(context):
+ return (context.space_data.view_type in
+ {'SEQUENCER', 'SEQUENCER_PREVIEW'})
+
+ @classmethod
+ def poll(self, context):
+ strip = functions.act_strip(context)
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return strip.type in ('META')
+ else:
+ return False
+
+ def execute(self, context):
+ active_strip = functions.act_strip(context)
+
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+
+ eco = prefs.eco_value
+ offset = prefs.eco_offset
+
+ active_strip.blend_type = 'REPLACE'
+ active_strip.blend_alpha = 1
+ for i in range(eco):
+ bpy.ops.sequencer.duplicate(mode='TRANSLATION')
+ bpy.ops.transform.seq_slide(
+ value=(offset, 1), snap=False, snap_target='CLOSEST',
+ snap_point=(0, 0, 0), snap_align=False,
+ snap_normal=(0, 0, 0), release_confirm=False
+ )
+
+ active_strip = functions.act_strip(context)
+
+ if prefs.eco_use_add_blend_mode:
+ active_strip.blend_type = 'ADD'
+ active_strip.blend_alpha = 1 - 1 / eco
+ else:
+ active_strip.blend_type = 'ALPHA_OVER'
+ active_strip.blend_alpha = 1 / eco
+
+ bpy.ops.sequencer.select_all(action='TOGGLE')
+ bpy.ops.sequencer.select_all(action='TOGGLE')
+ bpy.ops.sequencer.meta_make()
+
+ return {'FINISHED'}
diff --git a/sequencer_kinoraw_tools/exiftool.py b/sequencer_kinoraw_tools/exiftool.py
new file mode 100644
index 00000000..f42e3f34
--- /dev/null
+++ b/sequencer_kinoraw_tools/exiftool.py
@@ -0,0 +1,330 @@
+# -*- coding: utf-8 -*-
+# PyExifTool <http://github.com/smarnach/pyexiftool>
+# Copyright 2012 Sven Marnach
+
+# This file is part of PyExifTool.
+#
+# PyExifTool 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.
+#
+# PyExifTool 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 PyExifTool. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+PyExifTool is a Python library to communicate with an instance of Phil
+Harvey's excellent ExifTool_ command-line application. The library
+provides the class :py:class:`ExifTool` that runs the command-line
+tool in batch mode and features methods to send commands to that
+program, including methods to extract meta-information from one or
+more image files. Since ``exiftool`` is run in batch mode, only a
+single instance needs to be launched and can be reused for many
+queries. This is much more efficient than launching a separate
+process for every single query.
+
+.. _ExifTool: http://www.sno.phy.queensu.ca/~phil/exiftool/
+
+The source code can be checked out from the github repository with
+
+::
+
+ git clone git://github.com/smarnach/pyexiftool.git
+
+Alternatively, you can download a tarball_. There haven't been any
+releases yet.
+
+.. _tarball: https://github.com/smarnach/pyexiftool/tarball/master
+
+PyExifTool is licenced under GNU GPL version 3 or later.
+
+Example usage::
+
+ import exiftool
+
+ files = ["a.jpg", "b.png", "c.tif"]
+ with exiftool.ExifTool() as et:
+ metadata = et.get_metadata_batch(files)
+ for d in metadata:
+ print("{:20.20} {:20.20}".format(d["SourceFile"],
+ d["EXIF:DateTimeOriginal"]))
+"""
+
+from __future__ import unicode_literals
+
+import sys
+import subprocess
+import os
+import json
+import warnings
+import codecs
+
+try: # Py3k compatibility
+ basestring
+except NameError:
+ basestring = (bytes, str)
+
+executable = "exiftool"
+"""The name of the executable to run.
+
+If the executable is not located in one of the paths listed in the
+``PATH`` environment variable, the full path should be given here.
+"""
+
+# Sentinel indicating the end of the output of a sequence of commands.
+# The standard value should be fine.
+sentinel = b"{ready}"
+
+# The block size when reading from exiftool. The standard value
+# should be fine, though other values might give better performance in
+# some cases.
+block_size = 4096
+
+# This code has been adapted from Lib/os.py in the Python source tree
+# (sha1 265e36e277f3)
+
+
+def _fscodec():
+ encoding = sys.getfilesystemencoding()
+ errors = "strict"
+ if encoding != "mbcs":
+ try:
+ codecs.lookup_error("surrogateescape")
+ except LookupError:
+ pass
+ else:
+ errors = "surrogateescape"
+
+ def fsencode(filename):
+ """
+ Encode filename to the filesystem encoding with 'surrogateescape' error
+ handler, return bytes unchanged. On Windows, use 'strict' error handler
+ if the file system encoding is 'mbcs' (which is the default encoding).
+ """
+ if isinstance(filename, bytes):
+ return filename
+ else:
+ return filename.encode(encoding, errors)
+
+ return fsencode
+
+fsencode = _fscodec()
+del _fscodec
+
+
+class ExifTool(object):
+ """Run the `exiftool` command-line tool and communicate to it.
+
+ You can pass the file name of the ``exiftool`` executable as an
+ argument to the constructor. The default value ``exiftool`` will
+ only work if the executable is in your ``PATH``.
+
+ Most methods of this class are only available after calling
+ :py:meth:`start()`, which will actually launch the subprocess. To
+ avoid leaving the subprocess running, make sure to call
+ :py:meth:`terminate()` method when finished using the instance.
+ This method will also be implicitly called when the instance is
+ garbage collected, but there are circumstance when this won't ever
+ happen, so you should not rely on the implicit process
+ termination. Subprocesses won't be automatically terminated if
+ the parent process exits, so a leaked subprocess will stay around
+ until manually killed.
+
+ A convenient way to make sure that the subprocess is terminated is
+ to use the :py:class:`ExifTool` instance as a context manager::
+
+ with ExifTool() as et:
+ ...
+
+ .. warning:: Note that there is no error handling. Nonsensical
+ options will be silently ignored by exiftool, so there's not
+ much that can be done in that regard. You should avoid passing
+ non-existent files to any of the methods, since this will lead
+ to undefied behaviour.
+
+ .. py:attribute:: running
+
+ A Boolean value indicating whether this instance is currently
+ associated with a running subprocess.
+ """
+
+ def __init__(self, executable_=None):
+ if executable_ is None:
+ self.executable = executable
+ else:
+ self.executable = executable_
+ self.running = False
+
+ def start(self):
+ """Start an ``exiftool`` process in batch mode for this instance.
+
+ This method will issue a ``UserWarning`` if the subprocess is
+ already running. The process is started with the ``-G`` and
+ ``-n`` as common arguments, which are automatically included
+ in every command you run with :py:meth:`execute()`.
+ """
+ if self.running:
+ warnings.warn("ExifTool already running; doing nothing.")
+ return
+ with open(os.devnull, "w") as devnull:
+ self._process = subprocess.Popen(
+ [self.executable, "-stay_open", "True", "-@", "-",
+ "-common_args", "-G", "-u", "-a", "-n"],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=devnull)
+ self.running = True
+
+ def terminate(self):
+ """Terminate the ``exiftool`` process of this instance.
+
+ If the subprocess isn't running, this method will do nothing.
+ """
+ if not self.running:
+ return
+ self._process.stdin.write(b"-stay_open\nFalse\n")
+ self._process.stdin.flush()
+ self._process.communicate()
+ del self._process
+ self.running = False
+
+ def __enter__(self):
+ self.start()
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.terminate()
+
+ def __del__(self):
+ self.terminate()
+
+ def execute(self, *params):
+ """Execute the given batch of parameters with ``exiftool``.
+
+ This method accepts any number of parameters and sends them to
+ the attached ``exiftool`` process. The process must be
+ running, otherwise ``ValueError`` is raised. The final
+ ``-execute`` necessary to actually run the batch is appended
+ automatically; see the documentation of :py:meth:`start()` for
+ the common options. The ``exiftool`` output is read up to the
+ end-of-output sentinel and returned as a raw ``bytes`` object,
+ excluding the sentinel.
+
+ The parameters must also be raw ``bytes``, in whatever
+ encoding exiftool accepts. For filenames, this should be the
+ system's filesystem encoding.
+
+ .. note:: This is considered a low-level method, and should
+ rarely be needed by application developers.
+ """
+ if not self.running:
+ raise ValueError("ExifTool instance not running.")
+ self._process.stdin.write(b"\n".join(params + (b"-execute\n",)))
+ self._process.stdin.flush()
+ output = b""
+ fd = self._process.stdout.fileno()
+ while not output[-32:].strip().endswith(sentinel):
+ output += os.read(fd, block_size)
+ return output.strip()[:-len(sentinel)]
+
+ def execute_json(self, *params):
+ """Execute the given batch of parameters and parse the JSON output.
+
+ This method is similar to :py:meth:`execute()`. It
+ automatically adds the parameter ``-j`` to request JSON output
+ from ``exiftool`` and parses the output. The return value is
+ a list of dictionaries, mapping tag names to the corresponding
+ values. All keys are Unicode strings with the tag names
+ including the ExifTool group name in the format <group>:<tag>.
+ The values can have multiple types. All strings occurring as
+ values will be Unicode strings. Each dictionary contains the
+ name of the file it corresponds to in the key ``"SourceFile"``.
+
+ The parameters to this function must be either raw strings
+ (type ``str`` in Python 2.x, type ``bytes`` in Python 3.x) or
+ Unicode strings (type ``unicode`` in Python 2.x, type ``str``
+ in Python 3.x). Unicode strings will be encoded using
+ system's filesystem encoding. This behaviour means you can
+ pass in filenames according to the convention of the
+ respective Python version – as raw strings in Python 2.x and
+ as Unicode strings in Python 3.x.
+ """
+ params = map(fsencode, params)
+ return json.loads(self.execute(b"-j", *params).decode("utf-8"))
+
+ def get_metadata_batch(self, filenames):
+ """Return all meta-data for the given files.
+
+ The return value will have the format described in the
+ documentation of :py:meth:`execute_json()`.
+ """
+ return self.execute_json(*filenames)
+
+ def get_metadata(self, filename):
+ """Return meta-data for a single file.
+
+ The returned dictionary has the format described in the
+ documentation of :py:meth:`execute_json()`.
+ """
+ return self.execute_json(filename)[0]
+
+ def get_tags_batch(self, tags, filenames):
+ """Return only specified tags for the given files.
+
+ The first argument is an iterable of tags. The tag names may
+ include group names, as usual in the format <group>:<tag>.
+
+ The second argument is an iterable of file names.
+
+ The format of the return value is the same as for
+ :py:meth:`execute_json()`.
+ """
+ # Explicitly ruling out strings here because passing in a
+ # string would lead to strange and hard-to-find errors
+ if isinstance(tags, basestring):
+ raise TypeError("The argument 'tags' must be "
+ "an iterable of strings")
+ if isinstance(filenames, basestring):
+ raise TypeError("The argument 'filenames' must be "
+ "an iterable of strings")
+ params = ["-" + t for t in tags]
+ params.extend(filenames)
+ return self.execute_json(*params)
+
+ def get_tags(self, tags, filename):
+ """Return only specified tags for a single file.
+
+ The returned dictionary has the format described in the
+ documentation of :py:meth:`execute_json()`.
+ """
+ return self.get_tags_batch(tags, [filename])[0]
+
+ def get_tag_batch(self, tag, filenames):
+ """Extract a single tag from the given files.
+
+ The first argument is a single tag name, as usual in the
+ format <group>:<tag>.
+
+ The second argument is an iterable of file names.
+
+ The return value is a list of tag values or ``None`` for
+ non-existent tags, in the same order as ``filenames``.
+ """
+ data = self.get_tags_batch([tag], filenames)
+ result = []
+ for d in data:
+ d.pop("SourceFile")
+ result.append(next(iter(d.values()), None))
+ return result
+
+ def get_tag(self, tag, filename):
+ """Extract a single tag from a single file.
+
+ The return value is the value of the specified tag, or
+ ``None`` if this tag was not found in the file.
+ """
+ return self.get_tag_batch(tag, [filename])[0]
diff --git a/sequencer_kinoraw_tools/functions.py b/sequencer_kinoraw_tools/functions.py
new file mode 100644
index 00000000..2722f321
--- /dev/null
+++ b/sequencer_kinoraw_tools/functions.py
@@ -0,0 +1,455 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program 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 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+import os.path
+import operator
+import subprocess
+import random
+from bpy.props import (
+ IntProperty,
+ FloatProperty,
+ EnumProperty,
+ BoolProperty,
+ )
+
+imb_ext_image = [
+ # IMG
+ ".png", ".tga", ".bmp", ".jpg", ".jpeg", ".sgi", ".rgb",
+ ".rgba", ".tif", ".tiff", ".tx", ".jp2", ".hdr", ".dds",
+ ".dpx", ".cin", ".exr", ".rw2",
+ # IMG QT
+ ".gif", ".psd", ".pct", ".pict", ".pntg", ".qtif"
+ ]
+imb_ext_audio = [
+ ".wav", ".ogg", ".oga", ".mp3", ".mp2", ".ac3", ".aac",
+ ".flac", ".wma", ".eac3", ".aif", ".aiff", ".m4a"
+ ]
+imb_ext_movie = [
+ ".avi", ".flc", ".mov", ".movie", ".mp4", ".m4v", ".m2v",
+ ".m2t", ".m2ts", ".mts", ".mv", ".avs", ".wmv", ".ogv", ".ogg",
+ ".dv", ".mpeg", ".mpg", ".mpg2", ".vob", ".mkv", ".flv",
+ ".divx", ".xvid", ".mxf"
+ ]
+movieextdict = [
+ ("1", ".avi", ""),
+ ("2", ".flc", ""), ("3", ".mov", ""),
+ ("4", ".movie", ""), ("5", ".mp4", ""),
+ ("6", ".m4v", ""), ("7", ".m2v", ""),
+ ("8", ".m2t", ""), ("9", ".m2ts", ""),
+ ("10", ".mts", ""), ("11", ".mv", ""),
+ ("12", ".avs", ""), ("13", ".wmv", ""),
+ ("14", ".ogv", ""), ("15", ".dv", ""),
+ ("16", ".mpeg", ""), ("17", ".mpg", ""),
+ ("18", ".mpg2", ""), ("19", ".vob", ""),
+ ("20", ".mkv", ""), ("21", ".flv", ""),
+ ("22", ".divx", ""), ("23", ".xvid", ""),
+ ("24", ".mxf", "")
+ ]
+
+
+# Functions
+def error_handlers(self, op_name, errors, reports="ERROR"):
+ if self and reports:
+ self.report({'INFO'},
+ reports + ": some operations could not be performed "
+ "(See Console for more info)")
+
+ print("\n[Kinoraw Tools]\nOperator: {}\nWarning: {}\n".format(op_name, errors))
+
+
+def initSceneProperties(context):
+ # initSceneProperties is ONLY for varaibles that should
+ # be keeped with the blend file. Any other addon preferences
+ # should go to the addon preferences operator in __init__
+ try:
+ if context.scene.kr_scn_init is True:
+ return False
+ except AttributeError:
+ pass
+
+ scn = context.scene
+
+ # jump to cut
+ bpy.types.Scene.kr_auto_markers = BoolProperty(
+ name="kr_auto_markers",
+ description="Activate Auto markers",
+ default=False
+ )
+ scn.kr_auto_markers = False
+
+ bpy.types.Scene.kr_in_marker = IntProperty(
+ name="In",
+ description="In frame position",
+ min=-30000, max=30000,
+ default=1
+ )
+ scn.kr_in_marker = 1
+
+ bpy.types.Scene.kr_out_marker = IntProperty(
+ name="Out",
+ description="Out frame position",
+ min=scn.kr_in_marker, max=30000,
+ default=75
+ )
+ scn.kr_out_marker = 75
+
+ # sequencer extra actions
+ bpy.types.Scene.kr_default_fade_duration = IntProperty(
+ name="Duration",
+ description="Number of frames to fade",
+ min=1, max=250,
+ default=scn.render.fps
+ )
+ scn.kr_default_fade_duration = scn.render.fps
+
+ bpy.types.Scene.kr_default_fade_amount = FloatProperty(
+ name="Amount",
+ description="Maximum value of fade",
+ min=0.0,
+ max=100.0,
+ default=1.0
+ )
+ scn.kr_default_fade_amount = 1.0
+
+ # recursive loader
+ bpy.types.Scene.kr_recursive = BoolProperty(
+ name="Recursive",
+ description="Load in recursive folders",
+ default=False
+ )
+ scn.kr_recursive = False
+
+ bpy.types.Scene.kr_recursive_select_by_extension = BoolProperty(
+ name="Recursive ext",
+ description="Load only clips with selected extension",
+ default=False
+ )
+ scn.kr_recursive_select_by_extension = False
+
+ bpy.types.Scene.kr_default_ext = EnumProperty(
+ items=movieextdict,
+ name="ext enum",
+ default="3"
+ )
+ scn.kr_default_ext = "3"
+
+ bpy.types.Scene.kr_scn_init = BoolProperty(
+ name="Init",
+ default=False
+ )
+ scn.kr_scn_init = True
+
+ return True
+
+
+def get_selected_strips(context):
+ "return a list of selected strips"
+ strips = []
+ for i in context.scene.sequence_editor.sequences_all:
+ if i.select is True:
+ strips.append(i)
+ return strips
+
+
+def create_folder(path):
+ if not os.path.isdir(bpy.path.abspath(path)):
+ folder = bpy.path.abspath(path)
+ command = "mkdir " + folder
+ subprocess.call(command, shell=True)
+
+
+def add_marker(context, text, frame):
+ scn = context.scene
+ markers = scn.timeline_markers
+ mark = markers.new(name=text)
+ mark.frame = frame
+
+
+def act_strip(context):
+ try:
+ return context.scene.sequence_editor.active_strip
+ except AttributeError:
+ return None
+
+
+def detect_strip_type(filepath):
+ extension = os.path.splitext(filepath)[1]
+ extension = extension.lower()
+ if extension in imb_ext_image:
+ type = 'IMAGE'
+ elif extension in imb_ext_movie:
+ type = 'MOVIE'
+ elif extension in imb_ext_audio:
+ type = 'SOUND'
+ else:
+ type = None
+
+ return type
+
+
+# recursive load functions
+def getpathfrombrowser(context):
+ '''
+ returns path from filebrowser
+ '''
+ for a in context.window.screen.areas:
+ if a.type == 'FILE_BROWSER':
+ params = a.spaces[0].params
+ break
+ try:
+ params
+ except UnboundLocalError:
+ return {'CANCELLED'}
+
+ path = params.directory
+ return path
+
+
+def getfilepathfrombrowser(context):
+ """
+ returns path and file from filebrowser
+ """
+ for a in context.window.screen.areas:
+ if a.type == 'FILE_BROWSER':
+ params = a.spaces[0].params
+ break
+ try:
+ params
+ except UnboundLocalError:
+ return {'CANCELLED'}
+
+ if params.filename == '':
+ return {'CANCELLED'}
+
+ path = params.directory
+ filename = params.filename
+ return path, filename
+
+
+def setpathinbrowser(context, path, file):
+ '''
+ set path and file in the filebrowser
+ '''
+ for a in context.window.screen.areas:
+ if a.type == 'FILE_BROWSER':
+ params = a.spaces[0].params
+ break
+ try:
+ params
+ except UnboundLocalError:
+
+ return {'CANCELLED'}
+
+ params.directory = path
+ params.filename = file
+ return path, params
+
+
+def sortlist(filelist):
+ '''
+ given a list of tuplas (path, filename) returns a list sorted by filename
+ '''
+ filelist_sorted = sorted(filelist, key=operator.itemgetter(1))
+ return filelist_sorted
+
+
+def onefolder(context, recursive_select_by_extension, ext):
+ '''
+ returns a list of MOVIE type files from folder selected in file browser
+ '''
+ filelist = []
+ path, filename = getfilepathfrombrowser(context)
+
+ for i in movieextdict:
+ if i[0] == ext:
+ extension = i[1].rpartition(".")[2]
+ break
+
+ if detect_strip_type(path + filename) == 'MOVIE':
+ if recursive_select_by_extension is True:
+ # filtering by extension...
+ for file in os.listdir(path):
+ if file.rpartition(".")[2].lower() == extension:
+ filelist.append((path, file))
+ else:
+ # looking for all known extensions
+ for file in os.listdir(path):
+ for i in movieextdict:
+ if file.rpartition(".")[2].lower() == i[1].rpartition(".")[2]:
+ filelist.append((path, file))
+ return (filelist)
+
+
+def recursive(context, recursive_select_by_extension, ext):
+ '''
+ returns a list of MOVIE type files recursively from file browser
+ '''
+ filelist = []
+ path = getpathfrombrowser(context)
+
+ for i in movieextdict:
+ if i[0] == ext:
+ extension = i[1].rpartition(".")[2]
+ break
+
+ for root, dirs, files in os.walk(path):
+ for file in files:
+ if recursive_select_by_extension is True:
+ # filtering by extension...
+ if file.rpartition(".")[2].lower() == extension:
+ filelist.append((root, file))
+ else:
+ # looking for all known extensions
+ for i in movieextdict:
+ if file.rpartition(".")[2].lower() == i[1].rpartition(".")[2]:
+ filelist.append((root, file))
+ return filelist
+
+
+# jump to cut functions
+def triminout(strip, sin, sout):
+
+ """trim the strip to in and out, and returns
+ true if the strip is outside given in and out"""
+
+ start = strip.frame_start + strip.frame_offset_start - strip.frame_still_start
+ end = start + strip.frame_final_duration
+
+ remove = False
+ if end < sin:
+ remove = True
+ if start > sout:
+ remove = True
+
+ if end > sin:
+ if start < sin:
+ strip.select_right_handle = False
+ strip.select_left_handle = True
+ bpy.ops.sequencer.snap(frame=sin)
+ strip.select_left_handle = False
+ if start < sout:
+ if end > sout:
+ strip.select_left_handle = False
+ strip.select_right_handle = True
+ bpy.ops.sequencer.snap(frame=sout)
+ strip.select_right_handle = False
+
+ return remove
+
+
+# random editor functions
+
+def randompartition(lst, n, rand):
+ division = len(lst) / float(n)
+ lista = []
+ for i in range(n):
+ lista.append(division)
+
+ var = 0
+ for i in range(n - 1):
+ lista[i] += random.randint(-int(rand * division), int(rand * division))
+ var += lista[i]
+
+ if lista[n - 1] != len(lst) - var:
+ lista[n - 1] = len(lst) - var
+
+ random.shuffle(lista)
+ division = len(lst) / float(n)
+ count = 0
+ newlist = []
+ for i in range(n):
+ # print(lst[count : int(lista[i]-1)+count])
+ newlist.append([lst[count: int(lista[i] - 1) + count]])
+ count += int(lista[i])
+
+ return newlist
+
+
+def randomframe(strip):
+ # random frame between a and b
+ a = strip.frame_start
+ b = strip.frame_final_duration
+ rand = a + int(random.random() * b)
+
+ return rand
+
+
+# ???
+def get_matching_markers(scene, name=None):
+ '''
+ return a list of markers with same name
+ from the scene, or all markers if name is None
+ '''
+ selected_markers = []
+ markers = scene.timeline_markers
+ for mark in markers:
+ # print(mark.name, name)
+ if mark.name == name or name is None:
+ selected_markers.append(mark.frame)
+
+ return selected_markers
+
+
+def generate_subsets_list(number_of_subsets):
+ # generate marker subsets list
+ subset_list = []
+ subset_names = ['A', 'B', 'C', 'D', 'E', 'F']
+
+ for subset in range(number_of_subsets):
+ subset_list.append(subset_names[subset])
+ return subset_list
+
+
+def get_marker_dict(scene, number_of_subsets):
+ """
+ return a dict where:
+ keys = subset names
+ values = list of markers
+ """
+
+ subset_list = generate_subsets_list(number_of_subsets)
+ # generate dict with a list for each subset
+ marker_dict = {}
+
+ for subset in subset_list:
+ lists = get_matching_markers(scene, subset)
+ marker_dict[subset] = lists
+ return marker_dict
+
+
+def get_cut_dict(scene, number_of_subsets):
+ """
+ return a dict where:
+ keys = markers in the scene + start and end
+ values = duration in frames from key marker to next marker
+ """
+ # generate cut_list
+
+ lists = get_matching_markers(scene)
+ lists.append(scene.frame_start)
+ lists.append(scene.frame_end)
+ lists.sort()
+ cut_dict = {}
+
+ for i, j in enumerate(lists):
+ try:
+ cut_dict[j] = lists[i + 1] - j
+ except IndexError:
+ continue
+ return cut_dict
diff --git a/sequencer_kinoraw_tools/jumptocut.py b/sequencer_kinoraw_tools/jumptocut.py
new file mode 100644
index 00000000..d0be54a6
--- /dev/null
+++ b/sequencer_kinoraw_tools/jumptocut.py
@@ -0,0 +1,656 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program 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 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
+import bpy
+from . import functions
+from bpy.types import (
+ Operator,
+ )
+from bpy.props import (
+ IntProperty,
+ )
+from bpy.app.handlers import persistent
+
+
+class OBJECT_OT_Setinout(Operator):
+ bl_label = "Set IN and OUT to selected"
+ bl_idname = "sequencerextra.setinout"
+ bl_description = "Set IN and OUT markers to the selected strips limits"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor:
+ return scn.sequence_editor.active_strip
+ else:
+ return False
+
+ def execute(self, context):
+ functions.initSceneProperties(context)
+
+ scn = context.scene
+ markers = scn.timeline_markers
+ seq = scn.sequence_editor
+
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+
+ # search for timeline limits
+ tl_start = 300000
+ tl_end = -300000
+ for i in context.selected_editable_sequences:
+ if i.select is True:
+ start = i.frame_start + i.frame_offset_start - i.frame_still_start
+ end = start + i.frame_final_duration
+ if start < tl_start:
+ tl_start = start
+ if end > tl_end:
+ tl_end = end
+ # print(tl_start,tl_end)
+
+ if scn.kr_auto_markers:
+ scn.kr_in_marker = tl_start
+ scn.kr_out_marker = tl_end
+ else:
+ scn.kr_in_marker = tl_start
+ scn.kr_out_marker = tl_end
+
+ if "IN" in markers:
+ mark = markers["IN"]
+ mark.frame = scn.kr_in_marker
+ else:
+ mark = markers.new(name="IN")
+ mark.frame = scn.kr_in_marker
+
+ if "OUT" in markers:
+ mark = markers["OUT"]
+ mark.frame = scn.kr_out_marker
+ else:
+ mark = markers.new(name="OUT")
+ mark.frame = scn.kr_in_marker
+
+ return {'FINISHED'}
+
+
+class OBJECT_OT_Triminout(Operator):
+ bl_label = "Trim to in & out"
+ bl_idname = "sequencerextra.triminout"
+ bl_description = "Trim the selected strip to IN and OUT markers (if exists)"
+
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor:
+ if scn.sequence_editor.active_strip:
+ markers = scn.timeline_markers
+ if "IN" and "OUT" in markers:
+ return True
+ else:
+ return False
+
+ def execute(self, context):
+
+ scene = context.scene
+ seq = scene.sequence_editor
+
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+
+ markers = scene.timeline_markers
+ sin = markers["IN"].frame
+ sout = markers["OUT"].frame
+ strips = context.selected_editable_sequences
+
+ # (triminout function only works fine
+ # with one strip selected at a time)
+ for strip in strips:
+ # deselect all other strips
+ for i in strips:
+ i.select = False
+ # select current strip
+ strip.select = True
+ remove = functions.triminout(strip, sin, sout)
+ if remove is True:
+ bpy.ops.sequencer.delete()
+
+ # select all strips again
+ for strip in strips:
+ try:
+ strip.select = True
+ except ReferenceError:
+ pass
+
+ bpy.ops.sequencer.reload()
+
+ return {'FINISHED'}
+
+
+# SOURCE IN OUT
+class OBJECT_OT_Sourcein(Operator): # Operator source in
+ bl_label = "Source IN"
+ bl_idname = "sequencerextra.sourcein"
+ bl_description = "Add or move a marker named IN"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn:
+ return scn.sequence_editor
+ else:
+ return False
+
+ def execute(self, context):
+ functions.initSceneProperties(context)
+ scn = context.scene
+ markers = scn.timeline_markers
+
+ if scn.kr_auto_markers:
+ scn.kr_in_marker = scn.frame_current
+
+ else:
+ scn.kr_in_marker = scn.frame_current
+ if "IN" in markers:
+ mark = markers["IN"]
+ mark.frame = scn.kr_in_marker
+ else:
+ mark = markers.new(name="IN")
+ mark.frame = scn.kr_in_marker
+
+ # limit OUT marker position with IN marker
+ if scn.kr_in_marker > scn.kr_out_marker:
+ scn.kr_out_marker = scn.kr_in_marker
+
+ if "OUT" in markers:
+ mark = markers["OUT"]
+ mark.frame = scn.kr_out_marker
+
+ for m in markers:
+ m.select = False
+ if m.name in {"IN", "OUT"}:
+ m.select = True
+ bpy.ops.sequencer.reload()
+
+ return {'FINISHED'}
+
+
+class OBJECT_OT_Sourceout(Operator): # Operator source out
+ bl_label = "Source OUT"
+ bl_idname = "sequencerextra.sourceout"
+ bl_description = "Add or move a marker named OUT"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn:
+ return scn.sequence_editor
+ else:
+ return False
+
+ def execute(self, context):
+ scn = context.scene
+ functions.initSceneProperties(context)
+ markers = scn.timeline_markers
+
+ if scn.kr_auto_markers:
+ scn.kr_out_marker = scn.frame_current
+
+ else:
+ scn.kr_out_marker = scn.frame_current
+
+ # limit OUT marker position with IN marker
+ if scn.kr_out_marker < scn.kr_in_marker:
+ scn.kr_out_marker = scn.kr_in_marker
+
+ if "OUT" in markers:
+ mark = markers["OUT"]
+ mark.frame = scn.kr_out_marker
+ else:
+ mark = markers.new(name="OUT")
+ mark.frame = scn.kr_out_marker
+
+ for m in markers:
+ m.select = False
+ if m.name in {"IN", "OUT"}:
+ m.select = True
+ bpy.ops.sequencer.reload()
+ return {'FINISHED'}
+
+
+class OBJECT_OT_Setstartend(Operator): # Operator set start & end
+ bl_label = "Set Start and End"
+ bl_idname = "sequencerextra.setstartend"
+ bl_description = "Set Start and End to IN and OUT marker values"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ markers = scn.timeline_markers
+ if "IN" and "OUT" in markers:
+ return True
+ else:
+ return False
+
+ def execute(self, context):
+ functions.initSceneProperties(context)
+ scn = context.scene
+ markers = scn.timeline_markers
+ sin = markers["IN"]
+ sout = markers["OUT"]
+ scn.frame_start = sin.frame
+ scn.frame_end = sout.frame - 1
+ bpy.ops.sequencer.reload()
+
+ return {'FINISHED'}
+
+
+# Copy paste
+
+class OBJECT_OT_Metacopy(Operator): # Operator copy source in/out
+ bl_label = "Trim and Meta-Copy"
+ bl_idname = "sequencerextra.metacopy"
+ bl_description = ("Make meta from selected strips, trim it to in / out\n"
+ "(if available) and copy it to clipboard")
+
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ try:
+ # redo
+ scene = bpy.context.scene
+ seq = scene.sequence_editor
+ markers = scene.timeline_markers
+ strip1 = seq.active_strip
+
+ if strip1 is None:
+ self.report({'ERROR'}, "No strip selected")
+ return {"CANCELLED"}
+
+ if "IN" and "OUT" in markers:
+ sin = markers["IN"].frame
+ sout = markers["OUT"].frame
+ bpy.ops.sequencer.meta_make()
+ strip2 = seq.active_strip
+ functions.triminout(strip2, sin, sout)
+ bpy.ops.sequencer.copy()
+ bpy.ops.sequencer.meta_separate()
+ self.report({'INFO'}, "META2 has been trimed and copied")
+ else:
+ bpy.ops.sequencer.meta_make()
+ bpy.ops.sequencer.copy()
+ bpy.ops.sequencer.meta_separate()
+ self.report({'WARNING'}, "No In and Out!! META has been copied")
+
+ except Exception as e:
+ functions.error_handlers(self,
+ "sequencerextra.metacopy", e, "Trim and Meta-Copy")
+
+ return {"CANCELLED"}
+
+ return {'FINISHED'}
+
+
+class OBJECT_OT_Metapaste(Operator): # Operator paste source in/out
+ bl_label = "Paste in current Frame"
+ bl_idname = "sequencerextra.metapaste"
+ bl_description = "Paste source from clipboard to current frame"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ # redo
+ scene = bpy.context.scene
+ bpy.ops.sequencer.paste()
+ bpy.ops.sequencer.snap(frame=scene.frame_current)
+ strips = context.selected_editable_sequences
+ context.scene.sequence_editor.active_strip = strips[0]
+ context.scene.update()
+
+ return {'FINISHED'}
+
+
+# Operator paste source in/out
+class OBJECT_OT_Unmetatrim(Operator):
+ bl_label = "Paste in current Frame"
+ bl_idname = "sequencerextra.meta_separate_trim"
+ bl_description = "Unmeta and trim the content to meta duration"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor:
+ if scn.sequence_editor.active_strip:
+ return scn.sequence_editor.active_strip.type == "META"
+ else:
+ return False
+
+ def execute(self, context):
+ scn = context.scene
+ seq = scn.sequence_editor
+ markers = scn.timeline_markers
+
+ # setting in and out around meta
+ # while keeping data to restore in and out positions
+ strip = seq.active_strip
+ sin = strip.frame_start + strip.frame_offset_start
+ sout = sin + strip.frame_final_duration
+
+ borrarin = False
+ borrarout = False
+ original_in = 0
+ original_out = 0
+
+ if "IN" in markers:
+ original_in = markers["IN"].frame
+ markers["IN"].frame = sin
+ else:
+ mark = markers.new(name="IN")
+ mark.frame = sin
+ borrarin = True
+
+ if "OUT" in markers:
+ original_out = markers["OUT"].frame
+ markers["OUT"].frame = sout
+ else:
+ mark = markers.new(name="OUT")
+ mark.frame = sout
+ borrarout = True
+
+ # here starts the operator...
+
+ # get all META from selected strips
+ metastrips = []
+ for i in context.selected_editable_sequences:
+ if i.type == "META":
+ metastrips.append(i)
+
+ for meta in metastrips:
+ bpy.ops.sequencer.reload()
+
+ # deselect all strips
+ for i in context.selected_editable_sequences:
+ i.select = False
+
+ # make active current meta
+ meta.select = True
+ seq.active_strip = meta
+ bpy.ops.sequencer.reload()
+
+ # set in and out to meta
+ sin = meta.frame_start + meta.frame_offset_start
+ sout = sin + meta.frame_final_duration
+ # print("meta: ", sin, sout)
+
+ # grab meta content
+ newstrips = []
+ for i in meta.sequences:
+ newstrips.append(i)
+
+ # store meta channel
+ basechan = meta.channel
+ # look for upper and lower channels used by strips inside the meta
+ lowerchan = 32
+ upperchan = 0
+ for i in newstrips:
+ if i.channel < lowerchan:
+ lowerchan = i.channel
+ if i.channel > upperchan:
+ upperchan = i.channel
+
+ # calculate channel increment needed
+ deltachan = basechan - lowerchan
+ # reorder strips inside the meta
+ # before separate we need to store channel data
+ delta = upperchan - lowerchan + 1
+ for i in newstrips:
+ i.channel = i.channel + delta
+ chandict = {}
+ for i in newstrips:
+ i.channel = i.channel + deltachan - delta
+ chandict[i.name] = i.channel
+
+ """
+ for i in chandict:
+ print(i, chandict[i])
+ """
+ # go inside meta to trim strips
+ bpy.ops.sequencer.meta_toggle()
+
+ # update seq definition according to meta
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+
+ # create a list to store clips outside selection
+ # that will be removed
+ rmlist = []
+
+ # deselect all separated strips
+ for j in newstrips:
+ j.select = False
+ # print("newstrips: ",j.name, j.type)
+
+ # trim each strip separately
+ # first check special strips:
+ # (those who can move when any other does)
+ for i in newstrips:
+ if i.type in {"CROSS", "SPEED", "WIPE"}:
+ i.select = True
+ remove = functions.triminout(i, sin, sout)
+ if remove is True:
+ # print("checked: ",i.name, i.type)
+ rmlist.append(i)
+ i.select = False
+
+ # now for the rest of strips
+ for i in newstrips:
+ i.select = True
+ remove = functions.triminout(i, sin, sout)
+ if remove is True:
+ # print("checked: ",i.name, i.type)
+ rmlist.append(i)
+ i.select = False
+
+ # back outside the meta and separate it
+ bpy.ops.sequencer.meta_toggle()
+ bpy.ops.sequencer.meta_separate()
+
+ # reset seq definition
+ seq = scn.sequence_editor
+
+ # remove strips from outside the meta duration
+ for i in rmlist:
+ # print("removing: ",i.name, i.type)
+ for j in scn.sequence_editor.sequences_all:
+ j.select = False
+
+ i.select = True
+ scn.sequence_editor.active_strip = i
+ bpy.ops.sequencer.delete()
+
+ # select all strips and set one of the strips as active
+ for i in newstrips:
+ if i not in rmlist:
+ i.select = True
+ scn.sequence_editor.active_strip = i
+
+ bpy.ops.sequencer.reload()
+
+ # restore original IN and OUT values
+ if borrarin:
+ markers.remove(markers['IN'])
+ else:
+ markers["IN"].frame = original_in
+ if borrarout:
+ markers.remove(markers['OUT'])
+ else:
+ markers["OUT"].frame = original_out
+ scn.update()
+
+ return {'FINISHED'}
+
+
+class OBJECT_OT_Extrasnap(Operator): # Operator paste source in/out
+ bl_label = "Extra Snap"
+ bl_idname = "sequencerextra.extrasnap"
+ bl_description = "Snap the right, center or left of the strip to current frame"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # align: 0 = left snap, 1 = center snap, 2= right snap
+ align = IntProperty(
+ name="Align",
+ min=0, max=2,
+ default=1
+ )
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor:
+ return scn.sequence_editor.active_strip
+ else:
+ return False
+
+ def execute(self, context):
+ scene = bpy.context.scene
+ bpy.ops.sequencer.snap(frame=scene.frame_current)
+
+ if self.align != 0:
+ strips = context.selected_editable_sequences
+ for strip in strips:
+ if self.align == 1: # center snap
+ strip.frame_start -= strip.frame_final_duration / 2
+ else: # right snap
+ strip.frame_start -= strip.frame_final_duration
+
+ return {'FINISHED'}
+
+
+class OBJECT_OT_Extrahandles(Operator): # Operator paste source in/out
+ bl_label = "Extra Handles"
+ bl_idname = "sequencerextra.extrahandles"
+ bl_description = "Snap the right, center or left of the strip to current frame"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # side: 0 = left , 1 = both, 2= right
+ side = IntProperty(
+ name="Side",
+ min=0, max=2,
+ default=1
+ )
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor:
+ return scn.sequence_editor.active_strip
+ else:
+ return False
+
+ def execute(self, context):
+ strips = context.selected_editable_sequences
+
+ resetLeft = False
+ resetRight = False
+ changelistLeft = []
+ changelistRight = []
+
+ for strip in strips:
+ if self.side == 0 or self.side == 1:
+ if strip.select_left_handle:
+ resetLeft = True
+ changelistLeft.append(strip)
+ if self.side == 1 or self.side == 2:
+ if strip.select_right_handle:
+ resetRight = True
+ changelistRight.append(strip)
+
+ if len(changelistLeft) == len(strips):
+ resetLeft = False
+
+ if len(changelistRight) == len(strips):
+ resetRight = False
+
+ if ((len(changelistRight) != len(strips)) or
+ (len(changelistRight) != len(strips))) and \
+ self.side == 1:
+ resetLeft = True
+ resetRight = True
+
+ for strip in strips:
+ if resetLeft:
+ strip.select_left_handle = False
+
+ if self.side == 0 or self.side == 1:
+ if strip.select_left_handle:
+ strip.select_left_handle = False
+ else:
+ strip.select_left_handle = True
+
+ if resetRight:
+ strip.select_right_handle = False
+
+ if self.side == 1 or self.side == 2:
+ if strip.select_right_handle:
+ strip.select_right_handle = False
+ else:
+ strip.select_right_handle = True
+
+ return {'FINISHED'}
+
+
+@persistent
+def marker_handler(scn):
+ context = bpy.context
+ functions.initSceneProperties(context)
+
+ if scn.kr_auto_markers:
+ markers = scn.timeline_markers
+
+ if "IN" in markers:
+ mark = markers["IN"]
+ mark.frame = scn.kr_in_marker
+ else:
+ mark = markers.new(name="IN")
+ mark.frame = scn.kr_in_marker
+
+ if "OUT" in markers:
+ mark = markers["OUT"]
+ mark.frame = scn.kr_out_marker
+ else:
+ mark = markers.new(name="OUT")
+ mark.frame = scn.kr_out_marker
+
+ # limit OUT marker position with IN marker
+ if scn.kr_in_marker > scn.kr_out_marker:
+ scn.kr_out_marker = scn.kr_in_marker
+
+ return {'FINISHED'}
+ else:
+ return {'CANCELLED'}
+
+
+bpy.app.handlers.scene_update_post.append(marker_handler)
diff --git a/sequencer_kinoraw_tools/operators_extra_actions.py b/sequencer_kinoraw_tools/operators_extra_actions.py
new file mode 100644
index 00000000..1b52e3f2
--- /dev/null
+++ b/sequencer_kinoraw_tools/operators_extra_actions.py
@@ -0,0 +1,1302 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program 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 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+import os
+from bpy.types import Operator
+from bpy.props import (
+ IntProperty,
+ FloatProperty,
+ EnumProperty,
+ BoolProperty,
+ )
+from . import functions
+
+
+# Skip one second
+class Sequencer_Extra_FrameSkip(Operator):
+ bl_label = "Skip One Second"
+ bl_idname = "screenextra.frame_skip"
+ bl_description = "Skip through the Timeline by one-second increments"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ back = BoolProperty(
+ name="Back",
+ default=False
+ )
+
+ def execute(self, context):
+ one_second = bpy.context.scene.render.fps
+ if self.back is True:
+ one_second *= -1
+ bpy.ops.screen.frame_offset(delta=one_second)
+
+ return {'FINISHED'}
+
+
+# Trim timeline
+class Sequencer_Extra_TrimTimeline(Operator):
+ bl_label = "Trim to Timeline Content"
+ bl_idname = "timeextra.trimtimeline"
+ bl_description = "Automatically set start and end frames"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor:
+ return scn.sequence_editor.sequences
+ else:
+ return False
+
+ def execute(self, context):
+ scn = context.scene
+ seq = scn.sequence_editor
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+
+ frame_start = 300000
+ frame_end = -300000
+ for i in seq.sequences:
+ try:
+ if i.frame_final_start < frame_start:
+ frame_start = i.frame_final_start
+ if i.frame_final_end > frame_end:
+ frame_end = i.frame_final_end - 1
+ except AttributeError:
+ pass
+
+ if frame_start != 300000:
+ scn.frame_start = frame_start
+ if frame_end != -300000:
+ scn.frame_end = frame_end
+
+ bpy.ops.sequencer.view_all()
+
+ return {'FINISHED'}
+
+
+# Trim timeline to selection
+class Sequencer_Extra_TrimTimelineToSelection(Operator):
+ bl_label = "Trim to Selection"
+ bl_idname = "timeextra.trimtimelinetoselection"
+ bl_description = "Set start and end frames to selection"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor:
+ return scn.sequence_editor.sequences
+ else:
+ return False
+
+ def execute(self, context):
+ scn = context.scene
+ seq = scn.sequence_editor
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+
+ frame_start = 300000
+ frame_end = -300000
+ for i in seq.sequences:
+ try:
+ if i.frame_final_start < frame_start and i.select is True:
+ frame_start = i.frame_final_start
+ if i.frame_final_end > frame_end and i.select is True:
+ frame_end = i.frame_final_end - 1
+ except AttributeError:
+ pass
+
+ if frame_start != 300000:
+ scn.frame_start = frame_start
+ if frame_end != -300000:
+ scn.frame_end = frame_end
+
+ bpy.ops.sequencer.view_selected()
+ return {'FINISHED'}
+
+
+# Open image with editor and create movie clip strip
+"""
+ When a movie or image strip is selected, this operator creates a movieclip
+ or find the correspondent movieclip that already exists for this footage,
+ and add a VSE clip strip with same cuts the original strip has.
+ It can convert movie strips and image sequences, both with hard cuts or
+ soft cuts.
+"""
+
+
+class Sequencer_Extra_CreateMovieclip(Operator):
+ bl_label = "Create a Movieclip from selected strip"
+ bl_idname = "sequencerextra.createmovieclip"
+ bl_description = "Create a Movieclip strip from a MOVIE or IMAGE strip"
+
+ @classmethod
+ def poll(self, context):
+ strip = functions.act_strip(context)
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return strip.type in ('MOVIE', 'IMAGE')
+ else:
+ return False
+
+ def execute(self, context):
+ strip = functions.act_strip(context)
+ scn = context.scene
+
+ if strip.type == 'MOVIE':
+ path = strip.filepath
+ data_exists = False
+
+ for i in bpy.data.movieclips:
+ if i.filepath == path:
+ data_exists = True
+ data = i
+ newstrip = None
+ if data_exists is False:
+ try:
+ data = bpy.data.movieclips.load(filepath=path)
+ newstrip = bpy.ops.sequencer.movieclip_strip_add(
+ replace_sel=True, overlap=False,
+ clip=data.name
+ )
+ newstrip = functions.act_strip(context)
+ newstrip.frame_start = strip.frame_start\
+ - strip.animation_offset_start
+ tin = strip.frame_offset_start + strip.frame_start
+ tout = tin + strip.frame_final_duration
+ # print(newstrip.frame_start, strip.frame_start, tin, tout)
+ functions.triminout(newstrip, tin, tout)
+ except:
+ self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
+ return {'CANCELLED'}
+
+ else:
+ try:
+ newstrip = bpy.ops.sequencer.movieclip_strip_add(
+ replace_sel=True, overlap=False,
+ clip=data.name
+ )
+ newstrip = functions.act_strip(context)
+ newstrip.frame_start = strip.frame_start\
+ - strip.animation_offset_start
+ # i need to declare the strip this way in order
+ # to get triminout() working
+ clip = bpy.context.scene.sequence_editor.sequences[
+ newstrip.name
+ ]
+ # i cannot change these movie clip attributes via scripts
+ # but it works in the python console...
+ # clip.animation_offset_start = strip.animation.offset_start
+ # clip.animation_offset_end = strip.animation.offset_end
+ # clip.frame_final_duration = strip.frame_final_duration
+ tin = strip.frame_offset_start + strip.frame_start
+ tout = tin + strip.frame_final_duration
+ # print(newstrip.frame_start, strip.frame_start, tin, tout)
+ functions.triminout(clip, tin, tout)
+ except:
+ self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
+ return {'CANCELLED'}
+
+ elif strip.type == 'IMAGE':
+ # print("image")
+ base_dir = bpy.path.abspath(strip.directory)
+ scn.frame_current = strip.frame_start - strip.animation_offset_start
+
+ # searching for the first frame of the sequencer. This is mandatory
+ # for hard cutted sequence strips to be correctly converted,
+ # avoiding to create a new movie clip if not needed
+ filename = sorted(os.listdir(base_dir))[0]
+ path = os.path.join(base_dir, filename)
+ # print(path)
+ data_exists = False
+ for i in bpy.data.movieclips:
+ # print(i.filepath, path)
+ if i.filepath == path:
+ data_exists = True
+ data = i
+ # print(data_exists)
+ if data_exists is False:
+ try:
+ data = bpy.data.movieclips.load(filepath=path)
+ newstrip = bpy.ops.sequencer.movieclip_strip_add(
+ replace_sel=True, overlap=False,
+ clip=data.name
+ )
+ newstrip = functions.act_strip(context)
+ newstrip.frame_start = strip.frame_start\
+ - strip.animation_offset_start
+ clip = bpy.context.scene.sequence_editor.sequences[
+ newstrip.name
+ ]
+ tin = strip.frame_offset_start + strip.frame_start
+ tout = tin + strip.frame_final_duration
+ # print(newstrip.frame_start, strip.frame_start, tin, tout)
+ functions.triminout(clip, tin, tout)
+ except:
+ self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
+ return {'CANCELLED'}
+ else:
+ try:
+ newstrip = bpy.ops.sequencer.movieclip_strip_add(
+ replace_sel=True, overlap=False,
+ clip=data.name
+ )
+ newstrip = functions.act_strip(context)
+ newstrip.frame_start = strip.frame_start\
+ - strip.animation_offset_start
+ # need to declare the strip this way in order
+ # to get triminout() working
+ clip = bpy.context.scene.sequence_editor.sequences[
+ newstrip.name
+ ]
+ # cannot change this atributes via scripts...
+ # but it works in the python console...
+ # clip.animation_offset_start = strip.animation.offset_start
+ # clip.animation_offset_end = strip.animation.offset_end
+ # clip.frame_final_duration = strip.frame_final_duration
+ tin = strip.frame_offset_start + strip.frame_start
+ tout = tin + strip.frame_final_duration
+ # print(newstrip.frame_start, strip.frame_start, tin, tout)
+ functions.triminout(clip, tin, tout)
+ except:
+ self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
+ return {'CANCELLED'}
+
+ # show the new clip in a movie clip editor, if available.
+ if strip.type == 'MOVIE' or 'IMAGE':
+ for a in context.window.screen.areas:
+ if a.type == 'CLIP_EDITOR':
+ a.spaces[0].clip = data
+
+ return {'FINISHED'}
+
+
+# Open image with editor
+class Sequencer_Extra_Edit(Operator):
+ bl_label = "Open with Editor"
+ bl_idname = "sequencerextra.edit"
+ bl_description = "Open with Movie Clip or Image Editor"
+
+ @classmethod
+ def poll(self, context):
+ strip = functions.act_strip(context)
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return strip.type in ('MOVIE', 'IMAGE')
+ else:
+ return False
+
+ def execute(self, context):
+ strip = functions.act_strip(context)
+ scn = context.scene
+ data_exists = False
+
+ if strip.type == 'MOVIE':
+ path = strip.filepath
+
+ for i in bpy.data.movieclips:
+ if i.filepath == path:
+ data_exists = True
+ data = i
+
+ if data_exists is False:
+ try:
+ data = bpy.data.movieclips.load(filepath=path)
+ except:
+ self.report({'ERROR_INVALID_INPUT'}, "Error loading file")
+ return {'CANCELLED'}
+
+ elif strip.type == 'IMAGE':
+ base_dir = bpy.path.abspath(strip.directory)
+ strip_elem = strip.strip_elem_from_frame(scn.frame_current)
+ elem_name = strip_elem.filename
+ path = base_dir + elem_name
+
+ for i in bpy.data.images:
+ if i.filepath == path:
+ data_exists = True
+ data = i
+
+ if data_exists is False:
+ try:
+ data = bpy.data.images.load(filepath=path)
+ except:
+ self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
+ return {'CANCELLED'}
+
+ if strip.type == 'MOVIE':
+ for a in context.window.screen.areas:
+ if a.type == 'CLIP_EDITOR':
+ a.spaces[0].clip = data
+ elif strip.type == 'IMAGE':
+ for a in context.window.screen.areas:
+ if a.type == 'IMAGE_EDITOR':
+ a.spaces[0].image = data
+
+ return {'FINISHED'}
+
+
+# Open image with external editor
+class Sequencer_Extra_EditExternally(Operator):
+ bl_label = "Open with External Editor"
+ bl_idname = "sequencerextra.editexternally"
+ bl_description = "Open with the default external image editor"
+
+ @classmethod
+ def poll(self, context):
+ strip = functions.act_strip(context)
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return strip.type == 'IMAGE'
+ else:
+ return False
+
+ def execute(self, context):
+ strip = functions.act_strip(context)
+ scn = context.scene
+ base_dir = bpy.path.abspath(strip.directory)
+ strip_elem = strip.strip_elem_from_frame(scn.frame_current)
+ path = base_dir + strip_elem.filename
+
+ try:
+ bpy.ops.image.external_edit(filepath=path)
+ except:
+ self.report({'ERROR_INVALID_INPUT'},
+ "Please specify an Image Editor in Preferences > File")
+ return {'CANCELLED'}
+
+ return {'FINISHED'}
+
+
+# File name to strip name
+class Sequencer_Extra_FileNameToStripName(Operator):
+ bl_label = "File Name to Selected Strips Name"
+ bl_idname = "sequencerextra.striprename"
+ bl_description = "Set strip name to input file name"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor:
+ return scn.sequence_editor.sequences
+ else:
+ return False
+
+ def execute(self, context):
+ scn = context.scene
+ seq = scn.sequence_editor
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+ selection = False
+ for i in seq.sequences:
+ if i.select is True:
+ if i.type == 'IMAGE' and not i.mute:
+ selection = True
+ i.name = i.elements[0].filename
+ if (i.type == 'SOUND' or i.type == 'MOVIE') and not i.mute:
+ selection = True
+ i.name = bpy.path.display_name_from_filepath(i.filepath)
+ if selection is False:
+ self.report({'ERROR_INVALID_INPUT'},
+ "No image or movie strip selected")
+ return {'CANCELLED'}
+ return {'FINISHED'}
+
+
+# Navigate up
+class Sequencer_Extra_NavigateUp(Operator):
+ bl_label = "Navigate Up"
+ bl_idname = "sequencerextra.navigateup"
+ bl_description = "Move to Parent Timeline"
+
+ @classmethod
+ def poll(self, context):
+ try:
+ if context.scene.sequence_editor.meta_stack:
+ return True
+ return False
+ except:
+ return False
+
+ def execute(self, context):
+ if (functions.act_strip(context)):
+ strip = functions.act_strip(context)
+ seq_type = strip.type
+ if seq_type == 'META':
+ context.scene.sequence_editor.active_strip = None
+
+ bpy.ops.sequencer.meta_toggle()
+ return {'FINISHED'}
+
+
+# Ripple delete
+class Sequencer_Extra_RippleDelete(Operator):
+ bl_label = "Ripple Delete"
+ bl_idname = "sequencerextra.rippledelete"
+ bl_description = "Delete a strip and shift back following ones"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return True
+ else:
+ return False
+
+ def execute(self, context):
+ scn = context.scene
+ seq = scn.sequence_editor
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+ # strip = functions.act_strip(context)
+ for strip in context.selected_editable_sequences:
+ cut_frame = strip.frame_final_start
+ next_edit = 300000
+ bpy.ops.sequencer.select_all(action='DESELECT')
+ strip.select = True
+ bpy.ops.sequencer.delete()
+ striplist = []
+ for i in seq.sequences:
+ try:
+ if (i.frame_final_start > cut_frame and
+ not i.mute):
+ if i.frame_final_start < next_edit:
+ next_edit = i.frame_final_start
+ if not i.mute:
+ striplist.append(i)
+ except AttributeError:
+ pass
+
+ if next_edit == 300000:
+ return {'FINISHED'}
+ ripple_length = next_edit - cut_frame
+ for i in range(len(striplist)):
+ str = striplist[i]
+ try:
+ if str.frame_final_start > cut_frame:
+ str.frame_start = str.frame_start - ripple_length
+ except AttributeError:
+ pass
+ bpy.ops.sequencer.reload()
+ return {'FINISHED'}
+
+
+# Ripple cut
+class Sequencer_Extra_RippleCut(Operator):
+ bl_label = "Ripple Cut"
+ bl_idname = "sequencerextra.ripplecut"
+ bl_description = "Move a strip to buffer and shift back following ones"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return True
+ else:
+ return False
+
+ def execute(self, context):
+ scn = context.scene
+ seq = scn.sequence_editor
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+ strip = functions.act_strip(context)
+ bpy.ops.sequencer.select_all(action='DESELECT')
+ strip.select = True
+ temp_cf = scn.frame_current
+ scn.frame_current = strip.frame_final_start
+ bpy.ops.sequencer.copy()
+ scn.frame_current = temp_cf
+
+ bpy.ops.sequencerextra.rippledelete()
+ return {'FINISHED'}
+
+
+# Insert
+class Sequencer_Extra_Insert(Operator):
+ bl_label = "Insert"
+ bl_idname = "sequencerextra.insert"
+ bl_description = ("Move active strip to current frame and shift "
+ "forward following ones")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ singlechannel = BoolProperty(
+ name="Single Channel",
+ default=False
+ )
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return True
+ else:
+ return False
+
+ def execute(self, context):
+ scn = context.scene
+ seq = scn.sequence_editor
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+ strip = functions.act_strip(context)
+ gap = strip.frame_final_duration
+ bpy.ops.sequencer.select_all(action='DESELECT')
+ current_frame = scn.frame_current
+
+ striplist = []
+ for i in seq.sequences:
+ try:
+ if (i.frame_final_start >= current_frame and
+ not i.mute):
+ if self.singlechannel is True:
+ if i.channel == strip.channel:
+ striplist.append(i)
+ else:
+ striplist.append(i)
+ except AttributeError:
+ pass
+ try:
+ bpy.ops.sequencerextra.selectcurrentframe('EXEC_DEFAULT',
+ mode='AFTER')
+ except:
+ self.report({'ERROR_INVALID_INPUT'}, "Execution Error, "
+ "check your Blender version")
+ return {'CANCELLED'}
+
+ for i in range(len(striplist)):
+ str = striplist[i]
+ try:
+ if str.select is True:
+ str.frame_start += gap
+ except AttributeError:
+ pass
+ try:
+ diff = current_frame - strip.frame_final_start
+ strip.frame_start += diff
+ except AttributeError:
+ pass
+
+ strip = functions.act_strip(context)
+ scn.frame_current += strip.frame_final_duration
+ bpy.ops.sequencer.reload()
+
+ return {'FINISHED'}
+
+
+# Copy strip properties
+class Sequencer_Extra_CopyProperties(Operator):
+ bl_label = "Copy Properties"
+ bl_idname = "sequencerextra.copyproperties"
+ bl_description = "Copy properties of active strip to selected strips"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ prop = EnumProperty(
+ name="Property",
+ items=[
+ # common
+ ('name', 'Name', ''),
+ ('blend_alpha', 'Opacity', ''),
+ ('blend_type', 'Blend Mode', ''),
+ ('animation_offset', 'Input - Trim Duration', ''),
+ # non-sound
+ ('use_translation', 'Input - Image Offset', ''),
+ ('crop', 'Input - Image Crop', ''),
+ ('proxy', 'Proxy / Timecode', ''),
+ ('strobe', 'Filter - Strobe', ''),
+ ('color_multiply', 'Filter - Multiply', ''),
+ ('color_saturation', 'Filter - Saturation', ''),
+ ('deinterlace', 'Filter - De-Interlace', ''),
+ ('flip', 'Filter - Flip', ''),
+ ('float', 'Filter - Convert Float', ''),
+ ('alpha_mode', 'Filter - Alpha Mode', ''),
+ ('reverse', 'Filter - Backwards', ''),
+ # sound
+ ('pan', 'Sound - Pan', ''),
+ ('pitch', 'Sound - Pitch', ''),
+ ('volume', 'Sound - Volume', ''),
+ ('cache', 'Sound - Caching', ''),
+ # image
+ ('directory', 'Image - Directory', ''),
+ # movie
+ ('mpeg_preseek', 'Movie - MPEG Preseek', ''),
+ ('stream_index', 'Movie - Stream Index', ''),
+ # wipe
+ ('wipe', 'Effect - Wipe', ''),
+ # transform
+ ('transform', 'Effect - Transform', ''),
+ # color
+ ('color', 'Effect - Color', ''),
+ # speed
+ ('speed', 'Effect - Speed', ''),
+ # multicam
+ ('multicam_source', 'Effect - Multicam Source', ''),
+ # effect
+ ('effect_fader', 'Effect - Effect Fader', ''),
+ ],
+ default='blend_alpha'
+ )
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return True
+ else:
+ return False
+
+ def execute(self, context):
+ strip = functions.act_strip(context)
+
+ scn = context.scene
+ seq = scn.sequence_editor
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+
+ for i in seq.sequences:
+ if (i.select is True and not i.mute):
+ try:
+ if self.prop == 'name':
+ i.name = strip.name
+ elif self.prop == 'blend_alpha':
+ i.blend_alpha = strip.blend_alpha
+ elif self.prop == 'blend_type':
+ i.blend_type = strip.blend_type
+ elif self.prop == 'animation_offset':
+ i.animation_offset_start = strip.animation_offset_start
+ i.animation_offset_end = strip.animation_offset_end
+ elif self.prop == 'use_translation':
+ i.use_translation = strip.use_translation
+ i.transform.offset_x = strip.transform.offset_x
+ i.transform.offset_y = strip.transform.offset_y
+ elif self.prop == 'crop':
+ i.use_crop = strip.use_crop
+ i.crop.min_x = strip.crop.min_x
+ i.crop.min_y = strip.crop.min_y
+ i.crop.max_x = strip.crop.max_x
+ i.crop.max_y = strip.crop.max_y
+ elif self.prop == 'proxy':
+ i.use_proxy = strip.use_proxy
+ p = strip.proxy.use_proxy_custom_directory # pep80
+ i.proxy.use_proxy_custom_directory = p
+ i.proxy.use_proxy_custom_file = strip.proxy.use_proxy_custom_file
+ i.proxy.build_100 = strip.proxy.build_100
+ i.proxy.build_25 = strip.proxy.build_25
+ i.proxy.build_50 = strip.proxy.build_50
+ i.proxy.build_75 = strip.proxy.build_75
+ i.proxy.directory = strip.proxy.directory
+ i.proxy.filepath = strip.proxy.filepath
+ i.proxy.quality = strip.proxy.quality
+ i.proxy.timecode = strip.proxy.timecode
+ i.proxy.use_overwrite = strip.proxy.use_overwrite
+ elif self.prop == 'strobe':
+ i.strobe = strip.strobe
+ elif self.prop == 'color_multiply':
+ i.color_multiply = strip.color_multiply
+ elif self.prop == 'color_saturation':
+ i.color_saturation = strip.color_saturation
+ elif self.prop == 'deinterlace':
+ i.use_deinterlace = strip.use_deinterlace
+ elif self.prop == 'flip':
+ i.use_flip_x = strip.use_flip_x
+ i.use_flip_y = strip.use_flip_y
+ elif self.prop == 'float':
+ i.use_float = strip.use_float
+ elif self.prop == 'alpha_mode':
+ i.alpha_mode = strip.alpha_mode
+ elif self.prop == 'reverse':
+ i.use_reverse_frames = strip.use_reverse_frames
+ elif self.prop == 'pan':
+ i.pan = strip.pan
+ elif self.prop == 'pitch':
+ i.pitch = strip.pitch
+ elif self.prop == 'volume':
+ i.volume = strip.volume
+ elif self.prop == 'cache':
+ i.use_memory_cache = strip.use_memory_cache
+ elif self.prop == 'directory':
+ i.directory = strip.directory
+ elif self.prop == 'mpeg_preseek':
+ i.mpeg_preseek = strip.mpeg_preseek
+ elif self.prop == 'stream_index':
+ i.stream_index = strip.stream_index
+ elif self.prop == 'wipe':
+ i.angle = strip.angle
+ i.blur_width = strip.blur_width
+ i.direction = strip.direction
+ i.transition_type = strip.transition_type
+ elif self.prop == 'transform':
+ i.interpolation = strip.interpolation
+ i.rotation_start = strip.rotation_start
+ i.use_uniform_scale = strip.use_uniform_scale
+ i.scale_start_x = strip.scale_start_x
+ i.scale_start_y = strip.scale_start_y
+ i.translation_unit = strip.translation_unit
+ i.translate_start_x = strip.translate_start_x
+ i.translate_start_y = strip.translate_start_y
+ elif self.prop == 'color':
+ i.color = strip.color
+ elif self.prop == 'speed':
+ i.use_default_fade = strip.use_default_fade
+ i.speed_factor = strip.speed_factor
+ i.use_as_speed = strip.use_as_speed
+ i.scale_to_length = strip.scale_to_length
+ i.multiply_speed = strip.multiply_speed
+ i.use_frame_blend = strip.use_frame_blend
+ elif self.prop == 'multicam_source':
+ i.multicam_source = strip.multicam_source
+ elif self.prop == 'effect_fader':
+ i.use_default_fade = strip.use_default_fade
+ i.effect_fader = strip.effect_fader
+ except:
+ pass
+
+ bpy.ops.sequencer.reload()
+ return {'FINISHED'}
+
+
+# Fade in and out
+class Sequencer_Extra_FadeInOut(Operator):
+ bl_idname = "sequencerextra.fadeinout"
+ bl_label = "Fade..."
+ bl_description = "Fade volume or opacity of active strip"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ mode = EnumProperty(
+ name='Direction',
+ items=(
+ ('IN', "Fade In...", ""),
+ ('OUT', "Fade Out...", ""),
+ ('INOUT', "Fade In and Out...", "")),
+ default='IN',
+ )
+
+ fade_duration = IntProperty(
+ name='Duration',
+ description='Number of frames to fade',
+ min=1, max=250,
+ default=25)
+ fade_amount = FloatProperty(
+ name='Amount',
+ description='Maximum value of fade',
+ min=0.0,
+ max=100.0,
+ default=1.0)
+
+ @classmethod
+ def poll(cls, context):
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return True
+ else:
+ return False
+
+ def execute(self, context):
+ seq = context.scene.sequence_editor
+ scn = context.scene
+ strip = seq.active_strip
+ tmp_current_frame = context.scene.frame_current
+
+ if strip.type == 'SOUND':
+ if(self.mode) == 'OUT':
+ scn.frame_current = strip.frame_final_end - self.fade_duration
+ strip.volume = self.fade_amount
+ strip.keyframe_insert('volume')
+ scn.frame_current = strip.frame_final_end
+ strip.volume = 0
+ strip.keyframe_insert('volume')
+ elif(self.mode) == 'INOUT':
+ scn.frame_current = strip.frame_final_start
+ strip.volume = 0
+ strip.keyframe_insert('volume')
+ scn.frame_current += self.fade_duration
+ strip.volume = self.fade_amount
+ strip.keyframe_insert('volume')
+ scn.frame_current = strip.frame_final_end - self.fade_duration
+ strip.volume = self.fade_amount
+ strip.keyframe_insert('volume')
+ scn.frame_current = strip.frame_final_end
+ strip.volume = 0
+ strip.keyframe_insert('volume')
+ else:
+ scn.frame_current = strip.frame_final_start
+ strip.volume = 0
+ strip.keyframe_insert('volume')
+ scn.frame_current += self.fade_duration
+ strip.volume = self.fade_amount
+ strip.keyframe_insert('volume')
+
+ else:
+ if(self.mode) == 'OUT':
+ scn.frame_current = strip.frame_final_end - self.fade_duration
+ strip.blend_alpha = self.fade_amount
+ strip.keyframe_insert('blend_alpha')
+ scn.frame_current = strip.frame_final_end
+ strip.blend_alpha = 0
+ strip.keyframe_insert('blend_alpha')
+ elif(self.mode) == 'INOUT':
+ scn.frame_current = strip.frame_final_start
+ strip.blend_alpha = 0
+ strip.keyframe_insert('blend_alpha')
+ scn.frame_current += self.fade_duration
+ strip.blend_alpha = self.fade_amount
+ strip.keyframe_insert('blend_alpha')
+ scn.frame_current = strip.frame_final_end - self.fade_duration
+ strip.blend_alpha = self.fade_amount
+ strip.keyframe_insert('blend_alpha')
+ scn.frame_current = strip.frame_final_end
+ strip.blend_alpha = 0
+ strip.keyframe_insert('blend_alpha')
+ else:
+ scn.frame_current = strip.frame_final_start
+ strip.blend_alpha = 0
+ strip.keyframe_insert('blend_alpha')
+ scn.frame_current += self.fade_duration
+ strip.blend_alpha = self.fade_amount
+ strip.keyframe_insert('blend_alpha')
+
+ scn.frame_current = tmp_current_frame
+
+ scn.kr_default_fade_duration = self.fade_duration
+ scn.kr_default_fade_amount = self.fade_amount
+ return{'FINISHED'}
+
+ def invoke(self, context, event):
+ scn = context.scene
+ functions.initSceneProperties(context)
+ self.fade_duration = scn.kr_default_fade_duration
+ self.fade_amount = scn.kr_default_fade_amount
+ return context.window_manager.invoke_props_dialog(self)
+
+
+# Extend to fill
+class Sequencer_Extra_ExtendToFill(Operator):
+ bl_idname = "sequencerextra.extendtofill"
+ bl_label = "Extend to Fill"
+ bl_description = "Extend active strip forward to fill adjacent space"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return True
+ else:
+ return False
+
+ def execute(self, context):
+ scn = context.scene
+ seq = scn.sequence_editor
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+ strip = functions.act_strip(context)
+ chn = strip.channel
+ stf = strip.frame_final_end
+ enf = 300000
+
+ for i in seq.sequences:
+ ffs = i.frame_final_start
+ if (i.channel == chn and ffs > stf):
+ if ffs < enf:
+ enf = ffs
+ if enf == 300000 and stf < scn.frame_end:
+ enf = scn.frame_end
+
+ if enf == 300000 or enf == stf:
+ self.report({'ERROR_INVALID_INPUT'}, 'Unable to extend')
+ return {'CANCELLED'}
+ else:
+ strip.frame_final_end = enf
+
+ bpy.ops.sequencer.reload()
+ return {'FINISHED'}
+
+
+# Place from file browser
+class Sequencer_Extra_PlaceFromFileBrowser(Operator):
+ bl_label = "Place"
+ bl_idname = "sequencerextra.placefromfilebrowser"
+ bl_description = "Place or insert active file from File Browser"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ insert = BoolProperty(
+ name="Insert",
+ default=False
+ )
+
+ def execute(self, context):
+ scn = context.scene
+ for a in context.window.screen.areas:
+ if a.type == 'FILE_BROWSER':
+ params = a.spaces[0].params
+ break
+ try:
+ params
+ except UnboundLocalError:
+ self.report({'ERROR_INVALID_INPUT'}, 'No visible File Browser')
+ return {'CANCELLED'}
+
+ if params.filename == '':
+ self.report({'ERROR_INVALID_INPUT'}, 'No file selected')
+ return {'CANCELLED'}
+
+ path = os.path.join(params.directory, params.filename)
+ frame = context.scene.frame_current
+ strip_type = functions.detect_strip_type(params.filename)
+
+ try:
+ if strip_type == 'IMAGE':
+ image_file = []
+ filename = {"name": params.filename}
+ image_file.append(filename)
+ f_in = scn.frame_current
+ f_out = f_in + scn.render.fps - 1
+ bpy.ops.sequencer.image_strip_add(files=image_file,
+ directory=params.directory, frame_start=f_in,
+ frame_end=f_out, relative_path=False)
+ elif strip_type == 'MOVIE':
+ bpy.ops.sequencer.movie_strip_add(filepath=path,
+ frame_start=frame, relative_path=False)
+ elif strip_type == 'SOUND':
+ bpy.ops.sequencer.sound_strip_add(filepath=path,
+ frame_start=frame, relative_path=False)
+ else:
+ self.report({'ERROR_INVALID_INPUT'}, 'Invalid file format')
+ return {'CANCELLED'}
+ except:
+ self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
+ return {'CANCELLED'}
+
+ if self.insert is True:
+ try:
+ striplist = []
+ for i in bpy.context.selected_editable_sequences:
+ if (i.select is True and i.type == "SOUND"):
+ striplist.append(i)
+ bpy.ops.sequencerextra.insert()
+ if striplist[0]:
+ striplist[0].frame_start = frame
+ except:
+ self.report({'ERROR_INVALID_INPUT'}, "Execution Error, "
+ "check your Blender version")
+ return {'CANCELLED'}
+ else:
+ strip = functions.act_strip(context)
+ scn.frame_current += strip.frame_final_duration
+ bpy.ops.sequencer.reload()
+
+ return {'FINISHED'}
+
+
+# Select strips on same channel
+class Sequencer_Extra_SelectSameChannel(Operator):
+ bl_label = "Select Strips on the Same Channel"
+ bl_idname = "sequencerextra.selectsamechannel"
+ bl_description = "Select strips on the same channel as active one"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return True
+ else:
+ return False
+
+ def execute(self, context):
+ scn = context.scene
+ seq = scn.sequence_editor
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+ bpy.ops.sequencer.select_active_side(side="LEFT")
+ bpy.ops.sequencer.select_active_side(side="RIGHT")
+
+ return {'FINISHED'}
+
+
+# Current-frame-aware select
+class Sequencer_Extra_SelectCurrentFrame(Operator):
+ bl_label = "Current-Frame-Aware Select"
+ bl_idname = "sequencerextra.selectcurrentframe"
+ bl_description = "Select strips according to current frame"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ mode = EnumProperty(
+ name='Mode',
+ items=(
+ ('BEFORE', 'Before Current Frame', ''),
+ ('AFTER', 'After Current Frame', ''),
+ ('ON', 'On Current Frame', '')),
+ default='BEFORE',
+ )
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor:
+ return scn.sequence_editor.sequences
+ else:
+ return False
+
+ def execute(self, context):
+ mode = self.mode
+ scn = context.scene
+ seq = scn.sequence_editor
+ cf = scn.frame_current
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+
+ if mode == 'AFTER':
+ for i in seq.sequences:
+ try:
+ if (i.frame_final_start >= cf and not i.mute):
+ i.select = True
+ except AttributeError:
+ pass
+ elif mode == 'ON':
+ for i in seq.sequences:
+ try:
+ if (i.frame_final_start <= cf and
+ i.frame_final_end > cf and
+ not i.mute):
+ i.select = True
+ except AttributeError:
+ pass
+ else:
+ for i in seq.sequences:
+ try:
+ if (i.frame_final_end < cf and not i.mute):
+ i.select = True
+ except AttributeError:
+ pass
+
+ return {'FINISHED'}
+
+
+# Select by type
+class Sequencer_Extra_SelectAllByType(Operator):
+ bl_label = "All by Type"
+ bl_idname = "sequencerextra.select_all_by_type"
+ bl_description = "Select all the strips of the same type"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ type = EnumProperty(
+ name="Strip Type",
+ items=(
+ ('ACTIVE', 'Same as Active Strip', ''),
+ ('IMAGE', 'Image', ''),
+ ('META', 'Meta', ''),
+ ('SCENE', 'Scene', ''),
+ ('MOVIE', 'Movie', ''),
+ ('SOUND', 'Sound', ''),
+ ('TRANSFORM', 'Transform', ''),
+ ('COLOR', 'Color', '')),
+ default='ACTIVE',
+ )
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor:
+ return scn.sequence_editor.sequences
+ else:
+ return False
+
+ def execute(self, context):
+ strip_type = self.type
+ scn = context.scene
+ seq = scn.sequence_editor
+ meta_level = len(seq.meta_stack)
+ if meta_level > 0:
+ seq = seq.meta_stack[meta_level - 1]
+ active_strip = functions.act_strip(context)
+ if strip_type == 'ACTIVE':
+ if active_strip is None:
+ self.report({'ERROR_INVALID_INPUT'},
+ 'No active strip')
+ return {'CANCELLED'}
+ strip_type = active_strip.type
+
+ striplist = []
+ for i in seq.sequences:
+ try:
+ if (i.type == strip_type and not i.mute):
+ striplist.append(i)
+ except AttributeError:
+ pass
+ for i in range(len(striplist)):
+ str = striplist[i]
+ try:
+ str.select = True
+ except AttributeError:
+ pass
+
+ return {'FINISHED'}
+
+
+# Open in movie clip editor from file browser
+class Clip_Extra_OpenFromFileBrowser(Operator):
+ bl_label = "Open from File Browser"
+ bl_idname = "clipextra.openfromfilebrowser"
+ bl_description = "Load a Movie or Image Sequence from File Browser"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ for a in context.window.screen.areas:
+ if a.type == 'FILE_BROWSER':
+ params = a.spaces[0].params
+ break
+ try:
+ params
+ except:
+ self.report({'ERROR_INVALID_INPUT'}, 'No visible File Browser')
+ return {'CANCELLED'}
+
+ if params.filename == '':
+ self.report({'ERROR_INVALID_INPUT'}, 'No file selected')
+ return {'CANCELLED'}
+
+ path = params.directory + params.filename
+ strip_type = functions.detect_strip_type(params.filename)
+ data_exists = False
+
+ if strip_type in ('MOVIE', 'IMAGE'):
+ for i in bpy.data.movieclips:
+ if i.filepath == path:
+ data_exists = True
+ data = i
+
+ if data_exists is False:
+ try:
+ data = bpy.data.movieclips.load(filepath=path)
+ except:
+ self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
+ return {'CANCELLED'}
+ else:
+ self.report({'ERROR_INVALID_INPUT'}, 'Invalid file format')
+ return {'CANCELLED'}
+
+ for a in context.window.screen.areas:
+ if a.type == 'CLIP_EDITOR':
+ a.spaces[0].clip = data
+
+ return {'FINISHED'}
+
+
+# Open in movie clip editor from sequencer
+class Clip_Extra_OpenActiveStrip(Operator):
+ bl_label = "Open Active Strip"
+ bl_idname = "clipextra.openactivestrip"
+ bl_description = "Load a Movie or Image Sequence from Sequence Editor"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ scn = context.scene
+ strip = functions.act_strip(context)
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return strip.type in ('MOVIE', 'IMAGE')
+ else:
+ return False
+
+ def execute(self, context):
+ strip = functions.act_strip(context)
+ data_exists = False
+
+ if strip.type == 'MOVIE':
+ path = strip.filepath
+ elif strip.type == 'IMAGE':
+ base_dir = bpy.path.relpath(strip.directory)
+ filename = strip.elements[0].filename
+ path = base_dir + '/' + filename
+ else:
+ self.report({'ERROR_INVALID_INPUT'}, 'Invalid file format')
+ return {'CANCELLED'}
+
+ for i in bpy.data.movieclips:
+ if i.filepath == path:
+ data_exists = True
+ data = i
+ if data_exists is False:
+ try:
+ data = bpy.data.movieclips.load(filepath=path)
+ except:
+ self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
+ return {'CANCELLED'}
+
+ for a in context.window.screen.areas:
+ if a.type == 'CLIP_EDITOR':
+ a.spaces[0].clip = data
+
+ return {'FINISHED'}
+
+
+# Jog / Shuttle
+class Sequencer_Extra_JogShuttle(Operator):
+ bl_label = "Jog/Shuttle"
+ bl_idname = "sequencerextra.jogshuttle"
+ bl_description = ("Jog through the current sequence\n"
+ "Left Mouse button to confirm, Right mouse\Esc to cancel")
+
+ def execute(self, context):
+ scn = context.scene
+ start_frame = scn.frame_start
+ end_frame = scn.frame_end
+ duration = end_frame - start_frame
+ diff = self.x - self.init_x
+ diff /= 5
+ diff = int(diff)
+ extended_frame = diff + (self.init_current_frame - start_frame)
+ looped_frame = extended_frame % (duration + 1)
+ target_frame = start_frame + looped_frame
+ context.scene.frame_current = target_frame
+
+ def modal(self, context, event):
+ if event.type == 'MOUSEMOVE':
+ self.x = event.mouse_x
+ self.execute(context)
+ elif event.type == 'LEFTMOUSE':
+ return {'FINISHED'}
+ elif event.type in ('RIGHTMOUSE', 'ESC'):
+ return {'CANCELLED'}
+
+ return {'RUNNING_MODAL'}
+
+ def invoke(self, context, event):
+ scn = context.scene
+ self.x = event.mouse_x
+ self.init_x = self.x
+ self.init_current_frame = scn.frame_current
+ self.execute(context)
+ context.window_manager.modal_handler_add(self)
+
+ return {'RUNNING_MODAL'}
diff --git a/sequencer_kinoraw_tools/proxy_tools.py b/sequencer_kinoraw_tools/proxy_tools.py
new file mode 100644
index 00000000..33804241
--- /dev/null
+++ b/sequencer_kinoraw_tools/proxy_tools.py
@@ -0,0 +1,344 @@
+# gpl: authors Carlos Padial, Turi Scandurra
+
+import bpy
+import os
+from bpy.types import (
+ Operator,
+ Panel,
+ )
+from bpy.props import IntProperty
+import subprocess
+from . import functions
+
+
+proxy_qualities = [
+ ("1", "25%", ""), ("2", "50%", ""),
+ ("3", "75%", ""), ("4", "100%", ""),
+ ("5", "none", "")
+ ]
+
+
+# Functions
+def setup_proxy(context, strip, size):
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+
+ # set up proxy settings
+ strip.use_proxy = True
+
+ if prefs.use_bi_custom_directory:
+ strip.use_proxy_custom_directory = True
+ filename = strip.filepath.rpartition("/")[2].rpartition(".")[0]
+ strip.proxy.directory = bpy.path.relpath(prefs.proxy_dir + filename)
+ else:
+ strip.use_proxy_custom_directory = False
+
+ if strip.use_proxy_custom_file is True:
+ strip.use_proxy_custom_file = False
+
+ strip.proxy.quality = prefs.quality
+ strip.proxy.timecode = prefs.timecode
+
+ if size == 5:
+ strip.use_proxy = False
+ strip.proxy.build_25 = False
+ strip.proxy.build_50 = False
+ strip.proxy.build_75 = False
+ strip.proxy.build_100 = False
+
+ else:
+ proxysuffix = proxy_qualities[size - 1][1].split("%")[0]
+
+ if (proxysuffix == "25"):
+ strip.proxy.build_25 = True
+ if (proxysuffix == "50"):
+ strip.proxy.build_50 = True
+ if (proxysuffix == "75"):
+ strip.proxy.build_75 = True
+ if (proxysuffix == "100"):
+ strip.proxy.build_100 = True
+
+ return {"FINISHED"}
+
+
+def create_proxy(context, strip, size, res):
+ # calculate proxy resolution
+ div = 4 / size
+ newres = (int(int(res[0]) / div), int(int(res[1]) / div))
+
+ preferences = context.user_preferences
+ proxy_dir = preferences.addons[__package__].preferences.proxy_dir
+ scripts = preferences.addons[__package__].preferences.proxy_scripts
+ ffmpeg_command = preferences.addons[__package__].preferences.ffmpeg_command
+
+ functions.create_folder(proxy_dir)
+
+ if scripts:
+ commands = []
+
+ # get filename
+ if strip.type == "MOVIE":
+ filename = bpy.path.abspath(strip.filepath)
+ proxysuffix = proxy_qualities[size - 1][1].split("%")[0]
+ proxy_dir = bpy.path.abspath(proxy_dir)
+ newfilename = os.path.join(proxy_dir, filename.rpartition("/")[2])
+ fileoutput = newfilename.rpartition(".")[0] + "-" + proxysuffix + ".avi"
+
+ # default value for ffmpeg_command = "fmpeg -i {} -vcodec mjpeg -qv 1 -s {}x{} -y {}"
+
+ command = ffmpeg_command.format(filename, newres[0], newres[1], fileoutput)
+ print(command)
+
+ if scripts:
+ commands.append(command)
+ else:
+ # check for existing file
+ if not os.path.isfile(fileoutput):
+ subprocess.call(command, shell=True)
+ else:
+ print("File already exists")
+
+ # set up proxy settings
+ strip.use_proxy = True
+ try:
+ strip.use_proxy_custom_file = True
+ strip.proxy.filepath = bpy.path.relpath(fileoutput)
+ except:
+ pass
+
+ if (proxysuffix == "25"):
+ strip.proxy.build_25 = True
+ if (proxysuffix == "50"):
+ strip.proxy.build_50 = True
+ if (proxysuffix == "75"):
+ strip.proxy.build_75 = True
+ if (proxysuffix == "100"):
+ strip.proxy.build_100 = True
+
+ if scripts:
+ return commands
+ else:
+ return None
+
+
+def create_proxy_scripts(scripts_dir, commands, strip_name=None):
+
+ functions.create_folder(bpy.path.abspath(scripts_dir))
+ for i in commands:
+ # print(i)
+ filename = "{}/proxy_script_{}.sh".format(scripts_dir, strip_name)
+ text_file = open(bpy.path.abspath(filename), "w")
+ # print(filename)
+ text_file.write(i)
+ text_file.close()
+
+
+# classes
+class CreateProxyOperator(Operator):
+ bl_idname = "sequencer.create_proxy_operator"
+ bl_label = "Create Proxy"
+ bl_description = ("Use ffmpeg to create a proxy from video\n"
+ "and setup proxies for selected strip")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ size = IntProperty(
+ name="Proxy Size",
+ default=1
+ )
+
+ @classmethod
+ def poll(self, context):
+ strip = functions.act_strip(context)
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return strip.type in ('MOVIE')
+ else:
+ return False
+
+ def execute(self, context):
+ preferences = context.user_preferences
+ proxy_scripts_path = preferences.addons[__package__].preferences.proxy_scripts_path
+
+ for strip in context.selected_editable_sequences:
+ # get resolution from active strip
+ try:
+ bpy.ops.sequencerextra.read_exif()
+ except:
+ pass
+
+ sce = context.scene
+ try:
+ res = sce['metadata'][0]['Composite:ImageSize'].split("x")
+ except:
+ res = (sce.render.resolution_x, sce.render.resolution_y)
+
+ commands = create_proxy(context, strip, self.size, res)
+
+ if commands is None:
+ # Update scene
+ context.scene.update()
+ newstrip = context.scene.sequence_editor.active_strip
+
+ # deselect all other strips
+ for i in context.selected_editable_sequences:
+ if i.name != newstrip.name:
+ i.select = False
+
+ # Update scene
+ context.scene.update()
+ else:
+ create_proxy_scripts(proxy_scripts_path, commands, strip.name)
+
+ return {'FINISHED'}
+
+
+class CreateBIProxyOperator(Operator):
+ bl_idname = "sequencer.create_bi_proxy_operator"
+ bl_label = "Create proxy with Blender Internal"
+ bl_description = "Use BI system to create a proxy"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ size = IntProperty(
+ name="Proxy Size",
+ default=1
+ )
+
+ @classmethod
+ def poll(self, context):
+ strip = functions.act_strip(context)
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return strip.type in ('MOVIE')
+ else:
+ return False
+
+ def execute(self, context):
+ try:
+ strips = functions.get_selected_strips(context)
+
+ for strip in strips:
+ # deselect all other strips
+ for i in strips:
+ i.select = False
+ # select current strip
+ strip.select = True
+ if strip.type == "MOVIE":
+ setup_proxy(context, strip, self.size)
+ except Exception as e:
+ functions.error_handlers(
+ self,
+ "sequencer.create_bi_proxy_operator", e,
+ "Create proxy with blender internal"
+ )
+ return {"CANCELLED"}
+
+ # select all strips again
+ for strip in strips:
+ try:
+ strip.select = True
+ except ReferenceError:
+ pass
+ bpy.ops.sequencer.reload()
+
+ return {'FINISHED'}
+
+
+class CreateProxyToolPanel(Panel):
+ bl_label = "Proxy Tools"
+ bl_idname = "OBJECT_PT_ProxyTool"
+ bl_space_type = 'SEQUENCE_EDITOR'
+ bl_region_type = 'UI'
+
+ @classmethod
+ def poll(self, context):
+ if context.space_data.view_type in {'SEQUENCER',
+ 'SEQUENCER_PREVIEW'}:
+ strip = functions.act_strip(context)
+ scn = context.scene
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ if prefs.use_proxy_tools:
+ return strip.type in ('MOVIE')
+ else:
+ return False
+
+ def draw_header(self, context):
+ layout = self.layout
+ layout.label(text="", icon="AUTO")
+
+ def draw(self, context):
+
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+
+ layout = self.layout
+ layout.prop(prefs, "use_internal_proxy", text="Use BI proxy builder")
+
+ strip = functions.act_strip(context)
+
+ if prefs.use_internal_proxy:
+ layout = self.layout
+ row = layout.row(align=True)
+ row.prop(prefs, "use_bi_custom_directory")
+
+ if prefs.use_bi_custom_directory:
+ row.prop(prefs, "proxy_dir", text="")
+ filename = strip.filepath.rpartition("/")[2].rpartition(".")[0]
+ layout.label("sample dir: //" + bpy.path.abspath(prefs.proxy_dir + filename))
+
+ layout = self.layout
+ col = layout.column()
+ col.label(text="Build JPEG quality")
+ col.prop(prefs, "quality")
+
+ if strip.type == 'MOVIE':
+ col = layout.column()
+ col.label(text="Use timecode index:")
+
+ col.prop(prefs, "timecode")
+
+ layout = self.layout
+ layout.label("Setup and create BI proxy:")
+ row = layout.row(align=True)
+
+ for i in range(4):
+ proxysuffix = proxy_qualities[i][1]
+ row.operator("sequencer.create_bi_proxy_operator",
+ text=proxysuffix).size = i + 1
+
+ layout = self.layout
+ layout.operator("sequencer.create_bi_proxy_operator",
+ text="Clear proxy sizes").size = 5
+
+ else:
+ layout = self.layout
+ layout.prop(prefs, "proxy_dir", text="Path for proxies")
+
+ layout = self.layout
+ layout.label("Create and import proxy from clip:")
+ row = layout.row(align=True)
+
+ layout = self.layout
+ layout.prop(prefs, "ffmpeg_command", text="command")
+
+ layout.label("{} = filename, with, height, fileoutput")
+ label = prefs.ffmpeg_command.format("filename", "with", "height", "fileoutput")
+ layout.label(label)
+
+ for i in range(4):
+ proxysuffix = proxy_qualities[i][1]
+ row.operator("sequencer.create_proxy_operator",
+ text=proxysuffix).size = i + 1
+
+ layout = self.layout
+ layout.prop(prefs, "proxy_scripts")
+
+ if prefs.proxy_scripts:
+ layout = self.layout
+ layout.prop(prefs, "proxy_scripts_path", text="Path for scripts")
+
+ layout = self.layout
+ box = layout.box()
+ box.prop(context.space_data, "proxy_render_size")
+ box.operator("sequencer.rebuild_proxy", text="Rebuild Proxies and TC")
diff --git a/sequencer_kinoraw_tools/random_editor.py b/sequencer_kinoraw_tools/random_editor.py
new file mode 100644
index 00000000..e52f3fc4
--- /dev/null
+++ b/sequencer_kinoraw_tools/random_editor.py
@@ -0,0 +1,138 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program 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 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# Note: the Operator LoadRandomEditOperator was removed since is not
+# working. If it is fixed, reimplemented it can be reintroduced later
+
+import bpy
+from bpy.types import (
+ Operator,
+ Panel,
+ )
+from . import functions
+
+
+# classes
+class RandomScratchOperator(Operator):
+ bl_idname = "sequencer.randomscratchoperator"
+ bl_label = "Random Scratch Operator"
+ bl_description = "Random Scratch Operator"
+
+ @classmethod
+ def poll(self, context):
+ strip = functions.act_strip(context)
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return strip.type in ('META')
+ else:
+ return False
+
+ def invoke(self, context, event):
+ preferences = context.user_preferences
+ random_frames = preferences.addons[__package__].preferences.random_frames
+
+ sce = context.scene
+ seq = sce.sequence_editor
+ markers = sce.timeline_markers
+
+ if seq:
+ strip = seq.active_strip
+ if strip is not None:
+ if "IN" and "OUT" in markers:
+ sin = markers["IN"].frame
+ sout = markers["OUT"].frame
+
+ # select active strip
+ strip = context.scene.sequence_editor.active_strip
+ stripname = strip.name
+ # collect strip names inside the meta
+ stripnames = []
+ stripnames.append(strip.name)
+ for i in seq.active_strip.sequences:
+ stripnames.append(i.name)
+ # get strip channel
+ channel = strip.channel
+ repeat = range(int((sout - sin) / random_frames))
+ print(sin, sout, sout - sin, (sout - sin) / random_frames, repeat)
+
+ for i in repeat:
+ # select all related strips
+ for j in stripnames:
+ strip = seq.sequences_all[j]
+ strip.select = True
+ strip = seq.sequences_all[stripname]
+ seq.active_strip = strip
+ # deselect all other strips
+ for j in context.selected_editable_sequences:
+ if j.name not in stripnames:
+ j.select = False
+ a = bpy.ops.sequencer.duplicate_move()
+ # select new strip
+ newstrip = seq.active_strip
+ # deselect all other strips
+
+ for j in context.selected_editable_sequences:
+ if j.name != newstrip.name:
+ j.select = False
+ # random cut
+ newstrip.frame_start = sin + i * random_frames
+ rand = functions.randomframe(newstrip)
+ functions.triminout(newstrip, rand, rand + random_frames)
+ newstrip.frame_start = i * random_frames + sin - newstrip.frame_offset_start
+ newstrip.channel = channel + 1
+ else:
+ self.report({'WARNING'}, "There is no IN and OUT Markers")
+ bpy.ops.sequencer.reload()
+
+ return {'FINISHED'}
+
+
+class RandomEditorPanel(Panel):
+ bl_label = "Random Editor"
+ bl_idname = "OBJECT_PT_RandomEditor"
+ bl_space_type = 'SEQUENCE_EDITOR'
+ bl_region_type = 'UI'
+
+ @classmethod
+ def poll(self, context):
+ if context.space_data.view_type in {'SEQUENCER',
+ 'SEQUENCER_PREVIEW'}:
+ strip = functions.act_strip(context)
+ scn = context.scene
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ if prefs.use_random_editor:
+ return strip.type in ('META')
+ else:
+ return False
+
+ def draw_header(self, context):
+ layout = self.layout
+ layout.label(text="", icon="MOD_BUILD")
+
+ def draw(self, context):
+
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+
+ layout = self.layout
+ col = layout.column(align=True)
+ col.label("Cut duration:")
+ col.prop(prefs, "random_frames")
+ col.operator("sequencer.randomscratchoperator")
diff --git a/sequencer_kinoraw_tools/recursive_loader.py b/sequencer_kinoraw_tools/recursive_loader.py
new file mode 100644
index 00000000..22b34754
--- /dev/null
+++ b/sequencer_kinoraw_tools/recursive_loader.py
@@ -0,0 +1,266 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program 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 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+import os
+from bpy.types import (
+ Operator,
+ Panel,
+ )
+from bpy.props import (
+ EnumProperty,
+ BoolProperty,
+ )
+from . import functions
+from . import exiftool
+
+
+class Sequencer_Extra_RecursiveLoader(Operator):
+ bl_idname = "sequencerextra.recursiveload"
+ bl_label = "Recursive Load"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ recursive = BoolProperty(
+ name="Recursive",
+ description="Load in recursive folders",
+ default=False
+ )
+ recursive_select_by_extension = BoolProperty(
+ name="Select by extension",
+ description="Load only clips with selected extension",
+ default=False
+ )
+ ext = EnumProperty(
+ items=functions.movieextdict,
+ name="Extension",
+ default='3'
+ )
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor:
+ return (scn.sequence_editor)
+ else:
+ return False
+
+ def invoke(self, context, event):
+ scn = context.scene
+ try:
+ self.recursive = scn.kr_recursive
+ self.recursive_select_by_extension = scn.kr_recursive_select_by_extension
+ self.ext = scn.kr_default_ext
+ except AttributeError:
+ functions.initSceneProperties(context)
+ self.recursive = scn.kr_recursive
+ self.recursive_select_by_extension = scn.kr_recursive_select_by_extension
+ self.ext = scn.kr_default_ext
+
+ return context.window_manager.invoke_props_dialog(self)
+
+ def loader(self, context, filelist):
+ scn = context.scene
+ if filelist:
+ for i in filelist:
+ functions.setpathinbrowser(context, i[0], i[1])
+ try:
+ bpy.ops.sequencerextra.placefromfilebrowser()
+ except:
+ print("Error loading file (recursive loader error): ", i[1])
+ functions.add_marker(context, i[1], scn.frame_current)
+ self.report({'ERROR_INVALID_INPUT'}, 'Error loading file ')
+ pass
+
+ def execute(self, context):
+ scn = context.scene
+ if self.recursive is True:
+ # recursive
+ self.loader(
+ context, functions.sortlist(
+ functions.recursive(context, self.recursive_select_by_extension,
+ self.ext)
+ )
+ )
+ else:
+ # non recursive
+ self.loader(
+ context, functions.sortlist(functions.onefolder(
+ context, self.recursive_select_by_extension,
+ self.ext)
+ )
+ )
+ try:
+ scn.kr_recursive = self.recursive
+ scn.kr_recursive_select_by_extension = self.recursive_select_by_extension
+ scn.kr_default_ext = self.ext
+ except AttributeError:
+ functions.initSceneProperties(context)
+ self.recursive = scn.kr_recursive
+ self.recursive_select_by_extension = scn.kr_recursive_select_by_extension
+ self.ext = scn.kr_default_ext
+
+ return {'FINISHED'}
+
+
+# Read exif data
+# load exifdata from strip to scene['metadata'] property
+
+class Sequencer_Extra_ReadExifData(Operator):
+ bl_label = "Read EXIF Data"
+ bl_idname = "sequencerextra.read_exif"
+ bl_description = "Load exifdata from strip to metadata property in scene"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ scn = context.scene
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ return scn.sequence_editor.active_strip.type in ('IMAGE', 'MOVIE')
+ else:
+ return False
+
+ def execute(self, context):
+ try:
+ exiftool.ExifTool().start()
+ except:
+ self.report({'ERROR_INVALID_INPUT'}, "exiftool not found in PATH")
+
+ return {'CANCELLED'}
+
+ def getexifdata(strip):
+
+ def getexifvalues_image(lista):
+ metadata = []
+ with exiftool.ExifTool() as et:
+ try:
+ metadata = et.get_metadata_batch(lista)
+ except UnicodeDecodeError as Err:
+ print(Err)
+ # print(metadata[0])
+ print(len(metadata))
+ return metadata
+
+ def getexifvalues_movie(path):
+ metadata = []
+ with exiftool.ExifTool() as et:
+ try:
+ metadata = et.get_metadata_batch([path])
+ except UnicodeDecodeError as Err:
+ print(Err)
+ print(metadata[0])
+ print(len(metadata))
+ return metadata
+
+ def getlist(lista):
+ for root, dirs, files in os.walk(path):
+ for f in files:
+ if "." + f.rpartition(".")[2].lower() in \
+ functions.imb_ext_image:
+ lista.append(f)
+ """
+ if "." + f.rpartition(".")[2] in imb_ext_movie:
+ lista.append(f)
+ """
+ strip.elements
+ lista.sort()
+ return lista
+
+ if strip.type == "IMAGE":
+ path = bpy.path.abspath(strip.directory)
+ os.chdir(path)
+ # get a list of files
+ lista = []
+ for i in strip.elements:
+ lista.append(i.filename)
+ print(lista)
+ return getexifvalues_image(lista)
+
+ if strip.type == "MOVIE":
+ path = bpy.path.abspath(strip.filepath)
+ print([path])
+ return getexifvalues_movie(path)
+
+ sce = bpy.context.scene
+ strip = context.scene.sequence_editor.active_strip
+ sce['metadata'] = getexifdata(strip)
+
+ return {'FINISHED'}
+
+
+# TODO: fix poll to hide when unuseful
+
+class ExifInfoPanel(Panel):
+ """Creates a Panel in the Object properties window"""
+ bl_label = "EXIF Info Panel"
+ bl_space_type = 'SEQUENCE_EDITOR'
+ bl_region_type = 'UI'
+
+ @classmethod
+ def poll(self, context):
+ if context.space_data.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}:
+ strip = functions.act_strip(context)
+ scn = context.scene
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+
+ if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
+ if prefs.use_exif_panel:
+ return strip.type in ('MOVIE', 'IMAGE')
+ else:
+ return False
+
+ def draw_header(self, context):
+ layout = self.layout
+ layout.label(text="", icon="RADIO")
+
+ def draw(self, context):
+ layout = self.layout
+ sce = context.scene
+ row = layout.row()
+ row.operator("sequencerextra.read_exif")
+ row = layout.row()
+ row.label(text="Exif Data", icon='RENDER_REGION')
+ row = layout.row()
+
+ try:
+ strip = context.scene.sequence_editor.active_strip
+
+ f = strip.frame_start
+ frame = sce.frame_current
+ try:
+ if len(sce['metadata']) == 1:
+ for d in sce['metadata'][0]:
+ split = layout.split(percentage=0.5)
+ col = split.column()
+ row = col.row()
+ col.label(text=d)
+ col = split.column()
+ col.label(str(sce['metadata'][0][d]))
+ else:
+ for d in sce['metadata'][frame - f]:
+ split = layout.split(percentage=0.5)
+ col = split.column()
+ row = col.row()
+ col.label(text=d)
+ col = split.column()
+ col.label(str(sce['metadata'][frame - f][d]))
+
+ except (IndexError, KeyError):
+ pass
+ except AttributeError:
+ pass
diff --git a/sequencer_kinoraw_tools/ui.py b/sequencer_kinoraw_tools/ui.py
new file mode 100644
index 00000000..51b5de42
--- /dev/null
+++ b/sequencer_kinoraw_tools/ui.py
@@ -0,0 +1,765 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program 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 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+from bpy.types import (
+ Menu,
+ Panel,
+ )
+from . import functions
+
+
+# UI
+class SEQUENCER_EXTRA_MT_input(Menu):
+ bl_label = "Input"
+
+ def draw(self, context):
+ self.layout.label("Kinoraw Input")
+ self.layout.separator()
+
+ self.layout.operator_context = "INVOKE_REGION_WIN"
+ self.layout.operator("sequencerextra.striprename",
+ text="File Name to Strip Name")
+ self.layout.operator("sequencerextra.editexternally",
+ text="Open with External Editor")
+ self.layout.operator("sequencerextra.edit",
+ text="Open with Editor")
+ self.layout.operator("sequencerextra.createmovieclip",
+ text="Create Movieclip strip")
+
+
+def sequencer_header_func(self, context):
+ self.layout.menu("SEQUENCER_EXTRA_MT_input")
+
+
+def sequencer_add_menu_func(self, context):
+ self.layout.operator("sequencerextra.placefromfilebrowser",
+ text="Place from File Browser").insert = False
+ self.layout.operator("sequencerextra.placefromfilebrowser",
+ text="Insert from File Browser").insert = True
+ self.layout.operator("sequencerextra.recursiveload",
+ text="Recursive Load from Browser")
+ self.layout.separator()
+
+
+def sequencer_select_menu_func(self, context):
+ self.layout.operator_menu_enum('sequencerextra.select_all_by_type',
+ 'type', text='All by Type')
+ self.layout.separator()
+ self.layout.operator('sequencerextra.selectcurrentframe',
+ text='Before Current Frame').mode = 'BEFORE'
+ self.layout.operator('sequencerextra.selectcurrentframe',
+ text='After Current Frame').mode = 'AFTER'
+ self.layout.operator('sequencerextra.selectcurrentframe',
+ text='On Current Frame').mode = 'ON'
+ self.layout.separator()
+ self.layout.operator('sequencerextra.selectsamechannel',
+ text='Same Channel')
+
+
+def sequencer_strip_menu_func(self, context):
+ self.layout.operator('sequencerextra.extendtofill',
+ text='Extend to Fill')
+ self.layout.operator_menu_enum('sequencerextra.fadeinout',
+ 'mode', text='Fade')
+ self.layout.operator_menu_enum('sequencerextra.copyproperties',
+ 'prop')
+
+ self.layout.operator("sequencerextra.insert",
+ text="Insert (Single Channel)").singlechannel = True
+ self.layout.operator("sequencerextra.insert",
+ text="Insert").singlechannel = False
+ self.layout.operator("sequencerextra.ripplecut",
+ text="Ripple Cut")
+ self.layout.operator("sequencerextra.rippledelete",
+ text="Ripple Delete")
+ self.layout.separator()
+
+
+def time_frame_menu_func(self, context):
+ self.layout.operator('timeextra.trimtimelinetoselection',
+ text='Trim to Selection')
+ self.layout.operator('timeextra.trimtimeline',
+ text='Trim to Timeline Content')
+ self.layout.separator()
+ self.layout.operator('screenextra.frame_skip',
+ text='Skip Forward One Second').back = False
+ self.layout.operator('screenextra.frame_skip',
+ text='Skip Back One Second').back = True
+ self.layout.separator()
+
+
+def time_header_func(self, context):
+ self.layout.operator('sequencerextra.jogshuttle',
+ text='Jog/Shuttle', icon='NDOF_TURN')
+
+
+def clip_header_func(self, context):
+ self.layout.operator('sequencerextra.jogshuttle',
+ text='Jog/Shuttle', icon='NDOF_TURN')
+
+
+def clip_clip_menu_func(self, context):
+ self.layout.operator('clipextra.openactivestrip',
+ text='Open Active Strip')
+ self.layout.operator('clipextra.openfromfilebrowser',
+ text='Open from File Browser')
+ self.layout.separator()
+
+
+def draw_color_balance(layout, color_balance):
+ layout = layout.split(percentage=0.33)
+ col = layout.column()
+ col.label(text="Lift:")
+ col.template_color_picker(color_balance, "lift", value_slider=True, cubic=True)
+ row = col.row()
+ row.prop(color_balance, "lift", text="")
+ row.prop(color_balance, "invert_lift", text="Invert")
+
+ col = layout.column()
+ col.label(text="Gamma:")
+ col.template_color_picker(color_balance, "gamma", value_slider=True,
+ lock_luminosity=True, cubic=True)
+ row = col.row()
+ row.prop(color_balance, "gamma", text="")
+ row.prop(color_balance, "invert_gamma", text="Invert")
+
+ col = layout.column()
+ col.label(text="Gain:")
+ col.template_color_picker(color_balance, "gain", value_slider=True,
+ lock_luminosity=True, cubic=True)
+ row = col.row()
+ row.prop(color_balance, "gain", text="")
+ row.prop(color_balance, "invert_gain", text="Invert")
+
+
+class JumptoCut(Panel):
+ bl_space_type = "SEQUENCE_EDITOR"
+ bl_region_type = "UI"
+ bl_label = "Jump to Cut"
+
+ COMPAT_ENGINES = {'BLENDER_RENDER'}
+
+ _frame_rate_args_prev = None
+ _preset_class = None
+
+ @staticmethod
+ def _draw_framerate_label(*args):
+ # avoids re-creating text string each draw
+ if JumptoCut._frame_rate_args_prev == args:
+ return JumptoCut._frame_rate_ret
+
+ fps, fps_base, preset_label = args
+
+ if fps_base == 1.0:
+ fps_rate = round(fps)
+ else:
+ fps_rate = round(fps / fps_base, 2)
+
+ # TODO: Change the following to iterate over existing presets
+ custom_framerate = (fps_rate not in {23.98, 24, 25, 29.97, 30, 50, 59.94, 60})
+
+ if custom_framerate is True:
+ fps_label_text = "Custom (%r fps)" % fps_rate
+ show_framerate = True
+ else:
+ fps_label_text = "%r fps" % fps_rate
+ show_framerate = (preset_label == "Custom")
+
+ JumptoCut._frame_rate_args_prev = args
+ JumptoCut._frame_rate_ret = args = (fps_label_text, show_framerate)
+ return args
+
+ @staticmethod
+ def draw_framerate(sub, rd):
+ if JumptoCut._preset_class is None:
+ JumptoCut._preset_class = bpy.types.RENDER_MT_framerate_presets
+
+ args = rd.fps, rd.fps_base, JumptoCut._preset_class.bl_label
+ fps_label_text, show_framerate = JumptoCut._draw_framerate_label(*args)
+
+ sub.menu("RENDER_MT_framerate_presets", text=fps_label_text)
+
+ if show_framerate:
+ sub.prop(rd, "fps")
+ sub.prop(rd, "fps_base", text="/")
+
+ @classmethod
+ def poll(self, context):
+ if context.space_data.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}:
+ scn = context.scene
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+ if scn and scn.sequence_editor:
+ if prefs.use_jumptocut:
+ return True
+ else:
+ return False
+
+ def draw_header(self, context):
+ layout = self.layout
+ layout.label(text="", icon="RENDER_ANIMATION")
+
+ def draw(self, context):
+ scn = context.scene
+ strip = functions.act_strip(context)
+
+ preferences = context.user_preferences
+ prefs = preferences.addons[__package__].preferences
+
+ layout = self.layout
+ layout = layout.box()
+ # jump to cut main controls
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ split = row.split(percentage=0.33, align=True)
+ box = split.box()
+
+ row = box.row(align=True)
+ row.label(icon='TIME', text="Jump(sec)")
+ row.operator('screenextra.frame_skip',
+ text="", icon='TRIA_LEFT').back = True
+ row.operator('screenextra.frame_skip',
+ text="", icon='TRIA_RIGHT').back = False
+
+ box = split.box()
+ row = box.row(align=True)
+ row.label(icon='IPO_BOUNCE', text="Cuts")
+ row.operator("sequencer.strip_jump", icon="PLAY_REVERSE", text="").next = False
+ row.operator("sequencer.strip_jump", icon='PLAY', text="").next = True
+
+ box = split.box()
+ row = box.row(align=True)
+ row.label(icon='MARKER_HLT', text="Marker")
+ row.operator("screen.marker_jump", icon="TRIA_LEFT", text="").next = False
+ row.operator("screen.marker_jump", icon='TRIA_RIGHT', text="").next = True
+
+ rd = scn.render
+ screen = context.screen
+ row = col.row(align=True)
+ split = row.split(percentage=0.33, align=True)
+ sub_box = split.box()
+ sub_row = sub_box.row(align=True)
+ sub_row.alignment = "CENTER"
+
+ sub_row.operator("screen.frame_jump", text="", icon='REW').end = False
+ sub_row.operator("screen.keyframe_jump", text="", icon='PREV_KEYFRAME').next = False
+
+ if not screen.is_animation_playing:
+ # if using JACK and A/V sync:
+ # hide the play-reversed button
+ # since JACK transport doesn't support reversed playback
+ if scn.sync_mode == 'AUDIO_SYNC' and \
+ context.user_preferences.system.audio_device == 'JACK':
+ sub = sub_row.row(align=True)
+ sub.scale_x = 2.0
+ sub.operator("screen.animation_play", text="", icon='PLAY')
+ else:
+ sub_row.operator("screen.animation_play", text="",
+ icon='PLAY_REVERSE').reverse = True
+ sub_row.operator("screen.animation_play", text="", icon='PLAY')
+ else:
+ sub = sub_row.row(align=True)
+ sub.scale_x = 2.0
+ sub.operator("screen.animation_play", text="", icon="PAUSE")
+ sub_row.operator("screen.keyframe_jump", text="", icon="NEXT_KEYFRAME").next = True
+ sub_row.operator("screen.frame_jump", text="", icon="FF").end = True
+
+ sub_box = split.box()
+ sub_box.prop(scn, "sync_mode", text="")
+ sub_box = split.box()
+ self.draw_framerate(sub_box, rd)
+
+ row = layout.row(align=True)
+ row.operator("sequencer.refresh_all")
+ row.operator("sequencer.rendersize", text="Set Render size")
+
+ row = layout.row(align=True)
+ row.operator("sequencerextra.setstartend", icon="PREVIEW_RANGE", text="IN/OUT")
+ row.operator("timeextra.trimtimelinetoselection",
+ text="Selection", icon="PREVIEW_RANGE")
+ row.operator("timeextra.trimtimeline", text="All", icon="PREVIEW_RANGE")
+
+ layout = self.layout
+
+ # panel setup
+ row = layout.row(align=True)
+ split = row.split(percentage=0.5)
+ sub_row = split.row(align=True)
+ sub_row.prop(prefs, "kr_show_tools", text="Tools", icon='SEQ_SEQUENCER')
+ if prefs.kr_show_tools:
+ sub_row.prop(prefs, "kr_mini_ui", text="Compact UI", toggle=True)
+
+ row = split.row()
+ row = row.split(percentage=0.33)
+ row.prop(prefs, "kr_show_info", text="", icon='VIEWZOOM')
+ row = row.split(percentage=0.5)
+ row.prop(prefs, "kr_extra_info", text="", icon='BORDERMOVE')
+ row = row.split(percentage=1)
+ row.prop(prefs, "kr_show_modifiers", text="", icon='RESTRICT_VIEW_OFF')
+
+ if prefs.kr_show_tools:
+ layout = self.layout
+ layout = layout.box()
+ # snap, handler selector and meta tools
+
+ if prefs.kr_mini_ui:
+ row = layout.row(align=True)
+ row.operator("sequencerextra.extrasnap", text="", icon="SNAP_ON").align = 0
+ row.operator("sequencerextra.extrasnap", text="", icon="SNAP_SURFACE").align = 1
+ row.operator("sequencerextra.extrasnap", text="", icon="SNAP_ON").align = 2
+
+ row.separator()
+ row.operator("sequencerextra.extrahandles", text="", icon="TRIA_LEFT").side = 0
+ row.operator("sequencerextra.extrahandles", text="", icon="PMARKER").side = 1
+ row.operator("sequencerextra.extrahandles", text="", icon="TRIA_RIGHT").side = 2
+
+ row.separator()
+ row.operator("sequencerextra.metacopy", icon="COPYDOWN", text="")
+ row.operator("sequencerextra.metapaste", icon="PASTEDOWN", text="")
+ row.separator()
+ row.operator("sequencerextra.meta_separate_trim", text="", icon="ALIGN")
+ row.separator()
+ row.prop(prefs, "use_io_tools", text="I/O Tools")
+
+ # In / Out tools
+ if prefs.use_io_tools:
+ row = layout.row(align=True)
+ if scn.kr_auto_markers is True:
+ row.prop(scn, "kr_auto_markers", text="", toggle=True, icon="SPACE2")
+
+ row.separator()
+ row.operator("sequencerextra.sourcein", icon="MARKER_HLT", text="")
+ row.prop(scn, "kr_in_marker")
+ row.operator("sequencerextra.sourceout", icon='MARKER_HLT', text="")
+ row.prop(scn, "kr_out_marker")
+ else:
+ row.prop(scn, "kr_auto_markers", text="Auto I/O", toggle=True, icon="SPACE2")
+ row.operator("sequencerextra.sourcein", icon="MARKER_HLT", text="IN")
+ row.operator("sequencerextra.sourceout", icon='MARKER_HLT', text="OUT")
+
+ row.separator()
+ row.operator("sequencerextra.setinout", icon="ARROW_LEFTRIGHT", text="")
+ row.operator("sequencerextra.triminout", icon="FULLSCREEN_EXIT", text="")
+
+ # miniUI extra actions
+ row = layout.row(align=True)
+ row.operator("sequencerextra.jogshuttle",
+ text="", icon="NDOF_TURN")
+ row.operator("sequencerextra.navigateup",
+ text="", icon="FILE_PARENT")
+ row.operator("sequencerextra.extendtofill",
+ text="", icon="STYLUS_PRESSURE")
+ row.operator("sequencerextra.placefromfilebrowser",
+ text="", icon="TRIA_DOWN").insert = False
+ row.operator("sequencerextra.placefromfilebrowser",
+ text="", icon="TRIA_RIGHT").insert = True
+ row.operator("sequencer.slip",
+ text="", icon="MOD_SHRINKWRAP")
+ row.operator_menu_enum("sequencerextra.fadeinout",
+ "mode", text="Fade", icon="MOD_ARRAY")
+ row.operator_menu_enum("sequencerextra.copyproperties",
+ "prop", text="Copy", icon="SCRIPT")
+
+ else:
+ row = layout.row(align=True)
+ row.label("Snap:")
+
+ row.operator("sequencerextra.extrasnap", text="Left", icon="SNAP_ON").align = 0
+ row.operator("sequencerextra.extrasnap", text="Center", icon="SNAP_SURFACE").align = 1
+ row.operator("sequencerextra.extrasnap", text="Right", icon="SNAP_ON").align = 2
+
+ row = layout.row(align=True)
+ row.label("Handlers:")
+ row.operator("sequencerextra.extrahandles",
+ text="Left", icon="TRIA_LEFT").side = 0
+ row.operator("sequencerextra.extrahandles",
+ text="Both", icon="PMARKER").side = 1
+ row.operator("sequencerextra.extrahandles",
+ text="Right", icon="TRIA_RIGHT").side = 2
+
+ box = layout.box()
+ col = box.column_flow(columns=3, align=True)
+ row1 = col.row(align=True)
+ row1.operator("sequencerextra.metacopy", icon="COPYDOWN", text="Meta Copy")
+ row2 = col.row(align=True)
+ row2.operator("sequencerextra.metapaste", icon='PASTEDOWN', text="Paste Snap")
+ row3 = col.row()
+ row3.operator("sequencerextra.meta_separate_trim",
+ text="unMeta & Trim", icon="ALIGN")
+
+ # in /out tools
+ box = layout.box()
+ col = box.column_flow(columns=3, align=True)
+ row1 = col.row(align=True)
+ row1.operator("sequencerextra.sourcein", icon="MARKER_HLT", text="Set IN")
+ row2 = col.row(align=True)
+ row2.operator("sequencerextra.sourceout", icon='MARKER_HLT', text="Set OUT")
+ row3 = col.row()
+ row3.operator("sequencerextra.setinout", icon="ARROW_LEFTRIGHT", text="Selected")
+
+ sub_col = box.split(percentage=0.67, align=True)
+ row4 = sub_col.row(align=True)
+
+ if scn.kr_auto_markers is False:
+ row4.prop(scn, "kr_auto_markers",
+ text="Auto Markers", toggle=True, icon="SPACE2")
+ else:
+ row4.prop(scn, "kr_auto_markers", text="", icon="SPACE2")
+ row4.prop(scn, "kr_in_marker")
+ row4.prop(scn, "kr_out_marker")
+ box4 = row4.box()
+ box4.scale_x = 0.25
+ box4.scale_y = 0.5
+ box4.label(text="", icon="BLANK1")
+
+ row5 = sub_col.row()
+ row5.operator("sequencerextra.triminout", icon="FULLSCREEN_EXIT",
+ text="Trim", emboss=True)
+
+ # UI extra actions
+ box = layout.box()
+ row = box.row(align=True)
+ row.operator("sequencerextra.jogshuttle",
+ text="Jog/Shuttle", icon="NDOF_TURN")
+ row.operator("sequencerextra.navigateup",
+ text="Navigate Up", icon="FILE_PARENT")
+ row.operator("sequencerextra.extendtofill",
+ text="Extend to Fill", icon="STYLUS_PRESSURE")
+
+ row = box.row(align=True)
+ row.operator("sequencerextra.placefromfilebrowser",
+ text="File Place", icon="TRIA_DOWN").insert = False
+ row.operator("sequencerextra.placefromfilebrowser",
+ text="File Insert", icon="TRIA_RIGHT").insert = True
+ row.operator("sequencer.slip",
+ text="Slip", icon="MOD_SHRINKWRAP")
+ row = layout.row(align=True)
+ row.operator_menu_enum("sequencerextra.fadeinout",
+ "mode", text="Fade", icon="MOD_ARRAY")
+ row.operator_menu_enum("sequencerextra.copyproperties",
+ "prop", icon="SCRIPT")
+
+ # INFO boxes
+ if strip is not None:
+ if prefs.kr_show_info:
+ layout = layout.box()
+ row = layout.split(percentage=0.075)
+ row.prop(prefs, "kr_show_info", text="", icon='VIEWZOOM', emboss=True)
+ row = row.split(percentage=0.3)
+ row.prop(strip, "type", text="")
+ row = row.split(percentage=1)
+ row.prop(strip, "name", text="")
+
+ # mute information
+ layout.active = (not strip.mute)
+
+ # basic info
+ row = layout.row()
+ row.prop(strip, "channel")
+ row.prop(strip, "frame_start")
+ row.prop(strip, "frame_final_duration")
+
+ # source info
+ row = layout.split(percentage=0.8)
+
+ if strip.type == 'MOVIE':
+ row.prop(strip, "filepath", text="")
+
+ if strip.type == 'SOUND':
+ # Note: sound strip has a different structure
+ sound = strip.sound
+ if sound is not None:
+ row.prop(sound, "filepath", text="")
+
+ if strip.type == 'IMAGE':
+ row.prop(strip, "directory", text="")
+ # Current element for the filename
+ elem = strip.strip_elem_from_frame(context.scene.frame_current)
+ if elem:
+ row = layout.row()
+ # strip.elements[0] could be a fallback
+ row.prop(elem, "filename", text="File")
+ row.operator("sequencer.change_path", text="change files")
+
+ if strip.type == 'COLOR':
+ row.prop(strip, "color", text="")
+
+ # trim info
+ if strip.type not in {"SPEED", "WIPE", "CROSS", "ADJUSTMENT"}:
+ row = row.split(percentage=1)
+ row.prop(prefs, "kr_show_trim", text="Trim", toggle=True)
+ if prefs.kr_show_trim:
+ box = layout.box()
+ if not isinstance(strip, bpy.types.EffectSequence):
+ row = box.row(align=True)
+ row.label(text="Hard:")
+ row.prop(strip, "animation_offset_start", text="Start")
+ row.prop(strip, "animation_offset_end", text="End")
+ row = box.row(align=True)
+ row.label(text="Soft:")
+ row.prop(strip, "frame_offset_start", text="Start")
+ row.prop(strip, "frame_offset_end", text="End")
+
+ row = layout.row()
+
+ # special strips info
+ if strip.type == 'SPEED':
+ row.prop(strip, "multiply_speed")
+
+ if strip.type in {'CROSS', 'GAMMA_CROSS', 'WIPE',
+ 'ALPHA_OVER', 'ALPHA_UNDER', 'OVER_DROP'}:
+ row.prop(strip, "use_default_fade", "Default Fade")
+ if not strip.use_default_fade:
+ row.prop(strip, "effect_fader", text="Effect fader")
+
+ if strip.type == 'GAUSSIAN_BLUR':
+ row.prop(strip, "size_x")
+ row.prop(strip, "size_y")
+
+ if strip.type == 'WIPE':
+ row = layout.row()
+ row.prop(strip, "transition_type", expand=True)
+ row = layout.row()
+ row.prop(strip, "direction", expand=True)
+ row.prop(strip, "blur_width", slider=True)
+ if strip.transition_type in {'SINGLE', 'DOUBLE'}:
+ row.prop(strip, "angle")
+
+ if strip.type == 'GLOW':
+ flow = layout.column_flow()
+ flow.prop(strip, "threshold", slider=True)
+ flow.prop(strip, "clamp", slider=True)
+ flow.prop(strip, "boost_factor")
+ flow.prop(strip, "blur_radius")
+
+ row = layout.row()
+ row.prop(strip, "quality", slider=True)
+ row.prop(strip, "use_only_boost")
+
+ if strip.type == 'SPEED':
+ row = layout.row()
+ row.prop(strip, "use_default_fade", "Stretch to input strip length")
+ if not strip.use_default_fade:
+ row.prop(strip, "use_as_speed")
+ if strip.use_as_speed:
+ layout.prop(strip, "speed_factor")
+ else:
+ layout.prop(strip, "speed_factor", text="Frame number")
+ layout.prop(strip, "scale_to_length")
+
+ if strip.type == 'TRANSFORM':
+ row = layout.row(align=True)
+ row.prop(strip, "interpolation")
+ row.prop(strip, "translation_unit")
+ row = layout.row(align=True)
+ row.prop(strip, "translate_start_x", text="Pos X")
+ row.prop(strip, "translate_start_y", text="Pos Y")
+
+ row = layout.row(align=True)
+ if strip.use_uniform_scale:
+ row.prop(strip, "scale_start_x", text="Scale")
+ else:
+ row.prop(strip, "scale_start_x", text="Scale X")
+ row.prop(strip, "scale_start_y", text="Scale Y")
+ row = layout.row(align=True)
+ row.prop(strip, "use_uniform_scale")
+ row.prop(strip, "rotation_start", text="Rotation")
+
+ if strip.type == 'MULTICAM':
+ layout.prop(strip, "multicam_source")
+
+ row = layout.row(align=True)
+ sub = row.row(align=True)
+ sub.scale_x = 2.0
+
+ sub.operator("screen.animation_play", text="", icon='PAUSE' if
+ context.screen.is_animation_playing else 'PLAY')
+
+ row.label("Cut To")
+ for i in range(1, strip.channel):
+ row.operator("sequencer.cut_multicam", text="%d" % i).camera = i
+
+ try:
+ if strip.input_count > 0:
+ col = layout.column()
+ col.prop(strip, "input_1")
+ if strip.input_count > 1:
+ col.prop(strip, "input_2")
+ except AttributeError:
+ pass
+
+ # extra info box:
+ if prefs.kr_extra_info:
+ layout = self.layout
+ box = layout.box()
+ if strip.type not in {'SOUND'}:
+ row = box.row(align=True)
+ sub = row.row(align=True)
+ # mute this box
+ box.active = (not strip.mute)
+ sub.prop(prefs, "kr_extra_info", text="", icon='BORDERMOVE', emboss=True)
+ sub.separator()
+ sub.prop(strip, "blend_alpha", text="Opacity", slider=True)
+ row.prop(strip, "mute", toggle=True, icon_only=True)
+ row.prop(strip, "lock", toggle=True, icon_only=True)
+
+ split = box.split(percentage=0.5)
+ left_box = split.box()
+ row = left_box.row()
+ row.prop(strip, "strobe")
+
+ col = left_box.column(align=True)
+ col.separator()
+ col.prop(strip, "use_flip_x", text="Flip X", toggle=True)
+ col.prop(strip, "use_flip_y", text="Flip Y", toggle=True)
+ col.prop(strip, "use_reverse_frames", text="Backwards", toggle=True)
+ col.prop(strip, "use_deinterlace", toggle=True)
+
+ right_box = split.box()
+ col = right_box.column()
+ col.prop(strip, "blend_type", icon='COLOR')
+ col.prop(strip, "alpha_mode")
+
+ col = right_box.column()
+ col.prop(strip, "color_saturation", text="Saturation")
+ col.prop(strip, "color_multiply", text="Multiply")
+ col.prop(strip, "use_float", text="Convert Float")
+
+ row = box.row(align=True)
+ row.prop(strip, "use_translation", text="Image Offset", icon="AXIS_TOP")
+ row.prop(strip, "use_crop", text="Image Crop", icon="BORDER_RECT")
+ if strip.use_translation:
+ row = box.row(align=True)
+ row.prop(strip.transform, "offset_x", text="X")
+ row.prop(strip.transform, "offset_y", text="Y")
+ if strip.use_crop:
+ row = box.row(align=True)
+ row.prop(strip.crop, "max_y")
+ row.prop(strip.crop, "min_x")
+ row.prop(strip.crop, "min_y")
+ row.prop(strip.crop, "max_x")
+
+ else:
+ # sound type
+ row = box.row()
+ row.prop(prefs, "kr_extra_info", text="", icon='BORDERMOVE', emboss=True)
+ sub = row.row(align=True)
+ sub.prop(strip, "volume")
+ sub.prop(strip, "mute", toggle=True, icon_only=True)
+ sub.prop(strip, "lock", toggle=True, icon_only=True)
+
+ sound = strip.sound
+ if sound is not None:
+ row = box.row()
+ if sound.packed_file:
+ row.operator("sound.unpack", icon='PACKAGE', text="Unpack")
+ else:
+ row.operator("sound.pack", icon='UGLYPACKAGE', text="Pack")
+
+ row.prop(sound, "use_memory_cache", toggle=True, icon="DISK_DRIVE")
+
+ row.prop(strip, "show_waveform", toggle=True, icon="RNDCURVE")
+
+ row = box.row(align=True)
+ row.prop(strip, "pitch")
+ row.prop(strip, "pan")
+
+ # modifiers
+ if strip.type != 'SOUND' and prefs.kr_show_modifiers:
+ sequencer = context.scene.sequence_editor
+ layout = self.layout
+ layout = layout.box()
+ # mute this box
+ layout.active = (not strip.mute)
+ row = layout.split(percentage=0.075)
+ row.prop(prefs, "kr_show_modifiers", text="",
+ icon='RESTRICT_VIEW_OFF', emboss=True)
+ row = row.split(percentage=0.40)
+ row.prop(strip, "use_linear_modifiers", text="Linear")
+ row = row.split(percentage=1)
+ row.operator_menu_enum("sequencer.strip_modifier_add", "type")
+
+ for mod in strip.modifiers:
+ box = layout.box()
+
+ row = box.row()
+ row.prop(mod, "show_expanded", text="", emboss=False)
+ row.prop(mod, "name", text="")
+
+ row.prop(mod, "mute", text="")
+
+ sub = row.row(align=True)
+ props = sub.operator("sequencer.strip_modifier_move", text="", icon='TRIA_UP')
+ props.name = mod.name
+ props.direction = 'UP'
+ props = sub.operator("sequencer.strip_modifier_move", text="", icon='TRIA_DOWN')
+ props.name = mod.name
+ props.direction = 'DOWN'
+
+ row.operator("sequencer.strip_modifier_remove", text="", icon='X',
+ emboss=False).name = mod.name
+
+ if mod.show_expanded:
+ row = box.row()
+ row.prop(mod, "input_mask_type", expand=True)
+
+ if mod.input_mask_type == 'STRIP':
+ sequences_object = sequencer
+ if sequencer.meta_stack:
+ sequences_object = sequencer.meta_stack[-1]
+ box.prop_search(mod, "input_mask_strip",
+ sequences_object, "sequences", text="Mask")
+ else:
+ box.prop(mod, "input_mask_id")
+ row = box.row()
+ row.prop(mod, "mask_time", expand=True)
+
+ if mod.type == 'COLOR_BALANCE':
+ box.prop(mod, "color_multiply")
+ draw_color_balance(box, mod.color_balance)
+ elif mod.type == 'CURVES':
+ box.template_curve_mapping(mod, "curve_mapping", type='COLOR')
+ elif mod.type == 'HUE_CORRECT':
+ box.template_curve_mapping(mod, "curve_mapping", type='HUE')
+ elif mod.type == 'BRIGHT_CONTRAST':
+ col = box.column()
+ col.prop(mod, "bright")
+ col.prop(mod, "contrast")
+ elif mod.type == 'WHITE_BALANCE':
+ col = box.column()
+ col.prop(mod, "white_value")
+ elif mod.type == 'TONEMAP':
+ col = box.column()
+ col.prop(mod, "tonemap_type")
+ if mod.tonemap_type == 'RD_PHOTORECEPTOR':
+ col.prop(mod, "intensity")
+ col.prop(mod, "contrast")
+ col.prop(mod, "adaptation")
+ col.prop(mod, "correction")
+ elif mod.tonemap_type == 'RH_SIMPLE':
+ col.prop(mod, "key")
+ col.prop(mod, "offset")
+ col.prop(mod, "gamma")
+
+ if "copy_modifiers" in dir(bpy.ops.sequencer):
+ row = layout.row(align=True)
+ row.operator("sequencer.copy_modifiers",
+ text="Copy Modifiers", icon='COPYDOWN')
+ row.operator("sequencer.paste_modifiers",
+ text="Paste Modifiers", icon='PASTEDOWN')