From e9f96e1d93a50e8b1a0faa360af63738956737cb Mon Sep 17 00:00:00 2001 From: meta-androcto Date: Sat, 15 Apr 2017 14:25:27 +1000 Subject: Initial commit kinoraw_tools by Carlos Padial T51109 T50357 --- kinoraw_tools/__init__.py | 335 ++++++++ kinoraw_tools/audio_tools.py | 360 ++++++++ kinoraw_tools/datamosh.py | 194 +++++ kinoraw_tools/eco.py | 140 ++++ kinoraw_tools/exiftool.py | 330 ++++++++ kinoraw_tools/functions.py | 439 ++++++++++ kinoraw_tools/jumptocut.py | 645 +++++++++++++++ kinoraw_tools/operators_extra_actions.py | 1316 ++++++++++++++++++++++++++++++ kinoraw_tools/proxy_tools.py | 333 ++++++++ kinoraw_tools/random_editor.py | 218 +++++ kinoraw_tools/recursive_loader.py | 261 ++++++ kinoraw_tools/ui.py | 757 +++++++++++++++++ 12 files changed, 5328 insertions(+) create mode 100644 kinoraw_tools/__init__.py create mode 100644 kinoraw_tools/audio_tools.py create mode 100644 kinoraw_tools/datamosh.py create mode 100644 kinoraw_tools/eco.py create mode 100644 kinoraw_tools/exiftool.py create mode 100644 kinoraw_tools/functions.py create mode 100644 kinoraw_tools/jumptocut.py create mode 100644 kinoraw_tools/operators_extra_actions.py create mode 100644 kinoraw_tools/proxy_tools.py create mode 100644 kinoraw_tools/random_editor.py create mode 100644 kinoraw_tools/recursive_loader.py create mode 100644 kinoraw_tools/ui.py diff --git a/kinoraw_tools/__init__.py b/kinoraw_tools/__init__.py new file mode 100644 index 00000000..58bcccac --- /dev/null +++ b/kinoraw_tools/__init__.py @@ -0,0 +1,335 @@ +# ##### 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), + "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 imp + imp.reload(jumptocut) + imp.reload(operators_extra_actions) + imp.reload(audio_tools) + imp.reload(proxy_tools) + imp.reload(recursive_loader) + imp.reload(eco) + imp.reload(random_editor) + imp.reload(ui) + imp.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 +import os.path +from bpy.types import Menu +from bpy.types import Header + +from bpy.props import IntProperty, StringProperty, BoolProperty, EnumProperty + + +class KinorawToolsAddon(bpy.types.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', + default = False) + kr_extra_info = BoolProperty( + name = 'show extra info', + 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 = 'jumptocut panel', + default = True) + use_io_tools = BoolProperty( + name = 'enable in and out tools in jumptocut 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 scritps', + 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 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 scritps', + 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) + + def draw(self, context): + + layout = self.layout + layout.prop(self, "use_jumptocut") + layout = self.layout + layout.prop(self, "use_proxy_tools") + layout = self.layout + layout.prop(self, "use_audio_tools") + layout = self.layout + layout.prop(self, "use_exif_panel") + layout = self.layout + layout.prop(self, "use_eco_tools") + layout = self.layout + layout.prop(self, "use_random_editor") + layout = self.layout + layout.prop(self, "use_glitch_panel") + + +# 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/kinoraw_tools/audio_tools.py b/kinoraw_tools/audio_tools.py new file mode 100644 index 00000000..d0278de0 --- /dev/null +++ b/kinoraw_tools/audio_tools.py @@ -0,0 +1,360 @@ +import bpy, os +from bpy.props import IntProperty, StringProperty, BoolProperty +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(bpy.types.Operator): + """ Use ffmpeg to extract audio from video and import it synced""" + bl_idname = "sequencer.extract_wav_operator" + bl_label = "Extract Wav from movie strip Operator" + + @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("ya existe") + + 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 + + #print(newstrip.name) + 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(bpy.types.Operator): + """get sync info from selected audio and video strip and store it into a text file """ + bl_idname = "sequencer.external_audio_set_sync" + bl_label = "set sync info" + + @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 != 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(bpy.types.Operator): + """ reload external audio synced to selected movie strip acording to info from a text file """ + bl_idname = "sequencer.external_audio_reload" + bl_label = "reload external audio" + + @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(bpy.types.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'}: + 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_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(bpy.types.Operator): + """ """ + bl_idname = "sequencer.openmeterbridge" + bl_label = "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/kinoraw_tools/datamosh.py b/kinoraw_tools/datamosh.py new file mode 100644 index 00000000..0dc9d015 --- /dev/null +++ b/kinoraw_tools/datamosh.py @@ -0,0 +1,194 @@ +import bpy, os +from bpy.props import IntProperty, StringProperty, BoolProperty +import subprocess + +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): + preferences = context.user_preferences + prefs = preferences.addons[__package__].preferences + + 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): + preferences = context.user_preferences + prefs = preferences.addons[__package__].preferences + + 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(bpy.types.Operator): + """ """ + bl_idname = "sequencer.createavi" + bl_label = "create avi file" + + @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 + + size = IntProperty( + name='proxysize', + default=1) + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + + preferences = context.user_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": + 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(bpy.types.Operator): + """ """ + bl_idname = "sequencer.createdatamosh" + bl_label = "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(bpy.types.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 + + strip = functions.act_strip(context) + + layout.prop(prefs,"all_keyframes") + layout.prop(prefs,"load_glitch") + + layout.operator("sequencer.createdatamosh") + + + + + + + + diff --git a/kinoraw_tools/eco.py b/kinoraw_tools/eco.py new file mode 100644 index 00000000..01d13f71 --- /dev/null +++ b/kinoraw_tools/eco.py @@ -0,0 +1,140 @@ +#---------------------------------------------------------- +# 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.props import IntProperty, BoolProperty + +from . import functions + + +class EcoPanel(bpy.types.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): + scn = bpy.context.scene + 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') : + active_strip = functions.act_strip(context) + layout = self.layout + row=layout.row() + row.prop(prefs, 'eco_value', text="Ecos") + row=layout.row() + row.prop(prefs, 'eco_offset', text="Offset") + row=layout.row() + row.prop(prefs, 'eco_use_add_blend_mode', text="use_add_blend_mode") + row=layout.row() + row.operator('sequencer.eco') + + + + +class OBJECT_OT_EcoOperator(bpy.types.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 + + scn = bpy.context.scene + sel_strips = bpy.context.selected_sequences + cur_camera = scn.camera + 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/kinoraw_tools/exiftool.py b/kinoraw_tools/exiftool.py new file mode 100644 index 00000000..f42e3f34 --- /dev/null +++ b/kinoraw_tools/exiftool.py @@ -0,0 +1,330 @@ +# -*- coding: utf-8 -*- +# 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 . + +""" +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 :. + 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 :. + + 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 :. + + 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/kinoraw_tools/functions.py b/kinoraw_tools/functions.py new file mode 100644 index 00000000..cdbac3de --- /dev/null +++ b/kinoraw_tools/functions.py @@ -0,0 +1,439 @@ +# ##### 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, operator, subprocess, random + +from bpy.props import IntProperty +from bpy.props import FloatProperty +from bpy.props import EnumProperty +from bpy.props import BoolProperty +from bpy.props import StringProperty + +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 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 == 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 == 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 + ''' + 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: + #print("no browser") + self.report({'ERROR_INVALID_INPUT'}, 'No visible File Browser') + return {'CANCELLED'} + path = params.directory + return path + +def getfilepathfrombrowser(context): + ''' + returns path and file from filebrowser + ''' + 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: + #print("no browser") + #self.report({'ERROR_INVALID_INPUT'}, 'No visible File Browser') + return {'CANCELLED'} + + if params.filename == '': + #print("no file selected") + #self.report({'ERROR_INVALID_INPUT'}, 'No file selected') + return {'CANCELLED'} + path = params.directory + filename = params.filename + return path, filename + +def setpathinbrowser(context, path, file): + ''' + set path and file in the filebrowser + ''' + 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: + #print("no browser") + self.report({'ERROR_INVALID_INPUT'}, 'No visible File Browser') + 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 + + scn = context.scene + + if detect_strip_type(path + filename) == 'MOVIE': + if recursive_select_by_extension == 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 + + scn = context.scene + for root, dirs, files in os.walk(path): + for file in files: + if recursive_select_by_extension == 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) + #print(a, a+b, rand) + 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==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''' + #get markers from scene + markers = scene.timeline_markers + subset_list = generate_subsets_list(number_of_subsets) + #generate dict with a list for each subset + marker_dict = {} + for subset in subset_list: + list=get_matching_markers(scene, subset) + marker_dict[subset] = list + 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 + + list=get_matching_markers(scene) + list.append(scene.frame_start) + list.append(scene.frame_end) + list.sort() + #print("lista:",len(list),list) + cut_dict ={} + for i, j in enumerate(list): + try: + #print(j,list[i+1]-1-j) + cut_dict[j] = list[i+1]-j + except IndexError: + continue + return cut_dict + + +def random_edit_from_random_subset(cut_dict, sources_dict): + '''return a random edit from random subsets''' + #generate subset list (strings) + subset_list = generate_subsets_list(number_of_subsets) + # copy sources_dict + random_edit = [] + for cut in sorted(cut_dict.keys()): + #escoge un subset al azar: + rand = random.randrange(number_of_subsets) + subset = subset_list[rand] + #check if source subset is empty + print(len(sources_dict[subset]),subset) + if len(sources_dict[subset]) == 0: + sources_dict[subset] = get_matching_markers(selected_scene, subset) + print("repeating "+ subset + " clips") + marker = sources_dict[subset].pop() + random_edit.append((cut, cut_dict[cut], subset, marker)) + return random_edit diff --git a/kinoraw_tools/jumptocut.py b/kinoraw_tools/jumptocut.py new file mode 100644 index 00000000..0ccd05e0 --- /dev/null +++ b/kinoraw_tools/jumptocut.py @@ -0,0 +1,645 @@ +# ##### 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.props import IntProperty, BoolProperty + +from bpy.app.handlers import persistent + + + +class OBJECT_OT_Setinout(bpy.types.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 == 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(bpy.types.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 == 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(bpy.types.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): + strip = functions.act_strip(context) + scn = context.scene + if scn: + return scn.sequence_editor + else: + return False + + def execute(self, context): + functions.initSceneProperties(context) + scn=context.scene + seq = scn.sequence_editor + 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(bpy.types.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): + strip = functions.act_strip(context) + scn = context.scene + if scn: + return scn.sequence_editor + else: + return False + + def execute(self, context): + scn=context.scene + functions.initSceneProperties(context) + seq = scn.sequence_editor + 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(bpy.types.Operator): #Operator set start & end + bl_label = "set Start & 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(bpy.types.Operator): #Operator copy source in/out + bl_label = "Trim & Meta-Copy" + bl_idname = "sequencerextra.metacopy" + bl_description = "make meta from selected strips, trim it to in/out (if available) and copy it to clipboard" + + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + # rehacer + scene =bpy.context.scene + seq = scene.sequence_editor + markers =scene.timeline_markers + strip1 = seq.active_strip + if strip1 != None: + 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 & Out!! META has been copied") + else: + self.report({'ERROR'}, "No strip selected") + return {'FINISHED'} + +class OBJECT_OT_Metapaste(bpy.types.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): + # rehacer + 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'} + + +class OBJECT_OT_Unmetatrim(bpy.types.Operator): #Operator paste source in/out + 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 == 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 == 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(bpy.types.Operator): #Operator paste source in/out + bl_label = "extrasnap" + bl_idname = "sequencerextra.extrasnap" + bl_description = "snap the right, center or left of the strip to current frame" + + # align: 0 = left snap, 1 = center snap, 2= right snap + align = IntProperty( + name='align', + min=0, max=2, + default=1) + + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + strip = functions.act_strip(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(bpy.types.Operator): #Operator paste source in/out + bl_label = "extrahandles" + bl_idname = "sequencerextra.extrahandles" + bl_description = "snap the right, center or left of the strip to current frame" + + # side: 0 = left , 1 = both, 2= right + side = IntProperty( + name='side', + min=0, max=2, + default=1) + + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + strip = functions.act_strip(context) + scn = context.scene + if scn and scn.sequence_editor: + return scn.sequence_editor.active_strip + else: + return False + + def execute(self, context): + scn=context.scene + 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) + #preferences = context.user_preferences + #prefs = preferences.addons[__package__].preferences + if scn.kr_auto_markers: + #scn = context.scene + + 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/kinoraw_tools/operators_extra_actions.py b/kinoraw_tools/operators_extra_actions.py new file mode 100644 index 00000000..cae4a5d4 --- /dev/null +++ b/kinoraw_tools/operators_extra_actions.py @@ -0,0 +1,1316 @@ +# ##### 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 random +import math +import os, sys + +from bpy.props import IntProperty +from bpy.props import FloatProperty +from bpy.props import EnumProperty +from bpy.props import BoolProperty +from bpy.props import StringProperty + +from . import functions +from . import exiftool + +# ------------------------------ + +# SKIP ONE SECOND +class Sequencer_Extra_FrameSkip(bpy.types.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 == True: + one_second *= -1 + bpy.ops.screen.frame_offset(delta=one_second) + return {'FINISHED'} + + +# TRIM TIMELINE +class Sequencer_Extra_TrimTimeline(bpy.types.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(bpy.types.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 == True: + frame_start = i.frame_final_start + if i.frame_final_end > frame_end and i.select == 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 +class Sequencer_Extra_CreateMovieclip(bpy.types.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' + + """ + 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. + """ + + @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': + #print("movie", strip.frame_start) + path = strip.filepath + #print(path) + data_exists = False + for i in bpy.data.movieclips: + if i.filepath == path: + data_exists = True + data = i + newstrip = None + if data_exists == 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 == 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(bpy.types.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 == 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 == 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(bpy.types.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(bpy.types.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 == 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 == False: + self.report({'ERROR_INVALID_INPUT'}, + 'No image or movie strip selected') + return {'CANCELLED'} + return {'FINISHED'} + + +# ------------------------------ + + +# NAVIGATE UP +class Sequencer_Extra_NavigateUp(bpy.types.Operator): + bl_label = 'Navigate Up' + bl_idname = 'sequencerextra.navigateup' + bl_description = 'Move to Parent Timeline' + + @classmethod + def poll(self, context): + strip = functions.act_strip(context) + try: + if context.scene.sequence_editor.meta_stack: + return True + else: + 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(bpy.types.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): + strip = functions.act_strip(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(bpy.types.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): + strip = functions.act_strip(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(bpy.types.Operator): + bl_label = 'Insert' + bl_idname = 'sequencerextra.insert' + bl_description = 'Move active strip to current frame and shift '\ + 'forward following ones' + singlechannel = BoolProperty( + name='Single Channel', + default=False) + bl_options = {'REGISTER', 'UNDO'} + + @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 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 == 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 == 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(bpy.types.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): + strip = functions.act_strip(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) + selectedstrips = context.selected_editable_sequences + + 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 == 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(bpy.types.Operator): + bl_idname = 'sequencerextra.fadeinout' + bl_label = 'Fade...' + bl_description = 'Fade volume or opacity of active strip' + mode = EnumProperty( + name='Direction', + items=( + ('IN', 'Fade In...', ''), + ('OUT', 'Fade Out...', ''), + ('INOUT', 'Fade In and Out...', '')), + default='IN', + ) + bl_options = {'REGISTER', 'UNDO'} + + 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(bpy.types.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(bpy.types.Operator): + bl_label = 'Place' + bl_idname = 'sequencerextra.placefromfilebrowser' + bl_description = 'Place or insert active file from File Browser' + insert = BoolProperty( + name='Insert', + default=False) + bl_options = {'REGISTER', 'UNDO'} + + 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 == True: + try: + striplist = [] + for i in bpy.context.selected_editable_sequences: + if (i.select == 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(bpy.types.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): + strip = functions.act_strip(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(bpy.types.Operator): + bl_label = 'Current-Frame-Aware Select' + bl_idname = 'sequencerextra.selectcurrentframe' + bl_description = 'Select strips according to current frame' + mode = EnumProperty( + name='Mode', + items=( + ('BEFORE', 'Before Current Frame', ''), + ('AFTER', 'After Current Frame', ''), + ('ON', 'On Current Frame', '')), + default='BEFORE', + ) + 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): + 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(bpy.types.Operator): + bl_label = 'All by Type' + bl_idname = 'sequencerextra.select_all_by_type' + bl_description = 'Select all the strips of the same type' + 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', + ) + 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): + 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 == 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(bpy.types.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'} + + strip = functions.act_strip(context) + 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 == 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(bpy.types.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 == 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(bpy.types.Operator): + bl_label = 'Jog/Shuttle' + bl_idname = 'sequencerextra.jogshuttle' + bl_description = 'Jog through current sequence' + + 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/kinoraw_tools/proxy_tools.py b/kinoraw_tools/proxy_tools.py new file mode 100644 index 00000000..099dda60 --- /dev/null +++ b/kinoraw_tools/proxy_tools.py @@ -0,0 +1,333 @@ +import bpy, os +from bpy.props import IntProperty, StringProperty, BoolProperty +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 == 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("ya existe") + + # set up proxy settings + strip.use_proxy = True + strip.use_proxy_custom_file = True + strip.proxy.filepath = bpy.path.relpath(fileoutput) + 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(bpy.types.Operator): + """ Use ffmpeg to create a proxy from video and setup proxies \ + for selected strip""" + bl_idname = "sequencer.create_proxy_operator" + bl_label = " Create proxy" + + size = IntProperty( + name='proxysize', + default=1) + bl_options = {'REGISTER', 'UNDO'} + + @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_dir = preferences.addons[__package__].preferences.proxy_dir + scripts = preferences.addons[__package__].preferences.proxy_scripts + proxy_scripts_path = preferences.addons[__package__].preferences.proxy_scripts_path + for strip in context.selected_editable_sequences: + + # get resolution from active strip + bpy.ops.sequencerextra.read_exif() + sce = context.scene + try: + res = sce['metadata'][0]['Composite:ImageSize'].split("x") + except: + res=(sce.render.resolution_x, sce.render.resolution_y) + #print(res) + + commands = create_proxy(context, strip, self.size, res) + + if commands == 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(bpy.types.Operator): + """ Use BI system to create a proxy""" + bl_idname = "sequencer.create_bi_proxy_operator" + bl_label = " Create proxy with blender internal" + + size = IntProperty( + name='proxysize', + default=1) + bl_options = {'REGISTER', 'UNDO'} + + @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 + 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) + #select all strips again + for strip in strips: + try: + strip.select=True + except ReferenceError: + pass + bpy.ops.sequencer.reload() + return {'FINISHED'} + + +class CreateProxyToolPanel(bpy.types.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/kinoraw_tools/random_editor.py b/kinoraw_tools/random_editor.py new file mode 100644 index 00000000..5735bb3e --- /dev/null +++ b/kinoraw_tools/random_editor.py @@ -0,0 +1,218 @@ +# ##### 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, os, random +from bpy.props import IntProperty, BoolProperty, StringProperty + +from . import functions + +# CLASSES + +class RandomScratchOperator(bpy.types.Operator): + """ Random Scratch Operator """ + bl_idname = "sequencer.randomscratchoperator" + bl_label = "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 != 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") + bpy.ops.sequencer.reload() + return {'FINISHED'} + + +class LoadRandomEditOperator(bpy.types.Operator): + """ Random Editor Operator """ + bl_idname = "sequencer.loadrandomeditoperator" + bl_label = "Random Editor 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): + + #generate sources_dict + sources_dict = functions.get_marker_dict(random_selected_scene, random_number_of_subsets) + print("sources: ",sources_dict) + + #generate cut_list + cut_dict = functions.get_cut_dict(currentscene, random_number_of_subsets) + print("cuts: ",cut_dict) + + #generate random_edit + random_edit = functions.random_edit_from_random_subset(cut_dict, sources_dict) + print("random edit") + for i in random_edit: print("fr {}: dur: {} / {} {}".format(i[0],i[1],i[2],i[3])) + + sce = bpy.context.scene + seq=sce.sequence_editor + markers=sce.timeline_markers + strip= seq.active_strip + stripname = strip.name + if "IN" and "OUT" in markers: + sin=markers["IN"].frame + sout=markers["OUT"].frame + # 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 random_edit: + # 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 bpy.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 bpy.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, MARKER, MARKER+DURATION) + 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") + bpy.ops.sequencer.reload() + return {'FINISHED'} + + +class RandomEditorPanel(bpy.types.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 + layout.label("______ cut duration:)") + layout = self.layout + layout.prop(prefs, "random_frames") + layout = self.layout + layout.operator("sequencer.randomscratchoperator") + layout = self.layout + layout.operator("sequencer.loadrandomeditoperator") + + + + diff --git a/kinoraw_tools/recursive_loader.py b/kinoraw_tools/recursive_loader.py new file mode 100644 index 00000000..82e4820e --- /dev/null +++ b/kinoraw_tools/recursive_loader.py @@ -0,0 +1,261 @@ +# ##### 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 random +import math +import os, sys + +from bpy.props import IntProperty +from bpy.props import FloatProperty +from bpy.props import EnumProperty +from bpy.props import BoolProperty +from bpy.props import StringProperty + +from . import functions +from . import exiftool + + +class Sequencer_Extra_RecursiveLoader(bpy.types.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 == 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 +class Sequencer_Extra_ReadExifData(bpy.types.Operator): + # load exifdata from strip to scene['metadata'] property + 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 + frame = sce.frame_current + text = bpy.context.active_object + strip = context.scene.sequence_editor.active_strip + sce['metadata'] = getexifdata(strip) + return {'FINISHED'} + + +class ExifInfoPanel(bpy.types.Panel): + """Creates a Panel in the Object properties window""" + """ TODO: fix poll to hide when unuseful""" + 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 \ No newline at end of file diff --git a/kinoraw_tools/ui.py b/kinoraw_tools/ui.py new file mode 100644 index 00000000..d9be3042 --- /dev/null +++ b/kinoraw_tools/ui.py @@ -0,0 +1,757 @@ +# ##### 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 + + + +# UI +class SEQUENCER_EXTRA_MT_input(bpy.types.Menu): + bl_label = "Input" + + def draw(self, context): + self.layout.operator_context = 'INVOKE_REGION_WIN' + self.layout.operator('sequencerextra.striprename', + text='File Name to Strip Name', icon='PLUGIN') + self.layout.operator('sequencerextra.editexternally', + text='Open with External Editor', icon='PLUGIN') + self.layout.operator('sequencerextra.edit', + text='Open with Editor', icon='PLUGIN') + self.layout.operator('sequencerextra.createmovieclip', + text='Create Movieclip strip', icon='PLUGIN') + +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', icon='PLUGIN').insert = False + self.layout.operator('sequencerextra.placefromfilebrowser', + text='insert from file browser', icon='PLUGIN').insert = True + self.layout.operator('sequencerextra.recursiveload', + text='recursive load from browser', icon='PLUGIN') + 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', icon='PLUGIN') + self.layout.separator() + self.layout.operator('sequencerextra.selectcurrentframe', + text='Before Current Frame', icon='PLUGIN').mode = 'BEFORE' + self.layout.operator('sequencerextra.selectcurrentframe', + text='After Current Frame', icon='PLUGIN').mode = 'AFTER' + self.layout.operator('sequencerextra.selectcurrentframe', + text='On Current Frame', icon='PLUGIN').mode = 'ON' + self.layout.separator() + self.layout.operator('sequencerextra.selectsamechannel', + text='Same Channel', icon='PLUGIN') + + +def sequencer_strip_menu_func(self, context): + self.layout.operator('sequencerextra.extendtofill', + text='Extend to Fill', icon='PLUGIN') + self.layout.operator_menu_enum('sequencerextra.fadeinout', + 'mode', text='Fade', icon='PLUGIN') + self.layout.operator_menu_enum('sequencerextra.copyproperties', + 'prop', icon='PLUGIN') + + self.layout.operator('sequencerextra.insert', + text='Insert (Single Channel)', icon='PLUGIN').singlechannel = True + self.layout.operator('sequencerextra.insert', + text='Insert', icon='PLUGIN').singlechannel = False + self.layout.operator('sequencerextra.ripplecut', + text='Ripple Cut', icon='PLUGIN') + self.layout.operator('sequencerextra.rippledelete', + text='Ripple Delete', icon='PLUGIN') + self.layout.separator() + + +def time_frame_menu_func(self, context): + self.layout.operator('timeextra.trimtimelinetoselection', + text='Trim to Selection', icon='PLUGIN') + self.layout.operator('timeextra.trimtimeline', + text='Trim to Timeline Content', icon='PLUGIN') + self.layout.separator() + self.layout.operator('screenextra.frame_skip', + text='Skip Forward One Second', icon='PLUGIN').back = False + self.layout.operator('screenextra.frame_skip', + text='Skip Back One Second', icon='PLUGIN').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', icon='PLUGIN') + self.layout.operator('clipextra.openfromfilebrowser', + text='Open from File Browser', icon='PLUGIN') + 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(bpy.types.Panel): + bl_space_type = "SEQUENCE_EDITOR" + bl_region_type = "UI" + bl_label = "Jump to Cut 6" + + 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'}: + strip = functions.act_strip(context) + 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 + + row=layout.row(align=True) + + row.label(icon='TIME', text="Secs") + row.operator('screenextra.frame_skip', + text="", icon='TRIA_LEFT').back = True + row.operator('screenextra.frame_skip', + text="", icon='TRIA_RIGHT').back = False + + row.separator() + + 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 + + row.separator() + + 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 = layout.row(align=True) + row.operator("screen.frame_jump", text="", icon='REW').end = False + 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 = row.row(align=True) + sub.scale_x = 2.0 + sub.operator("screen.animation_play", text="", icon='PLAY') + else: + row.operator("screen.animation_play", text="", icon='PLAY_REVERSE').reverse = True + row.operator("screen.animation_play", text="", icon='PLAY') + else: + sub = row.row(align=True) + sub.scale_x = 2.0 + sub.operator("screen.animation_play", text="", icon='PAUSE') + row.operator("screen.keyframe_jump", text="", icon='NEXT_KEYFRAME').next = True + row.operator("screen.frame_jump", text="", icon='FF').end = True + + row.separator() + row.prop(scn, "sync_mode", text="") + row.separator() + self.draw_framerate(row, rd) + + row=layout.row(align=True) + row.operator("sequencer.refresh_all") + row.operator('sequencer.rendersize', text='set render size', icon='PLUGIN') + + 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 + # layout = layout.box() + # panel setup ------------------------------------------------------ + row=layout.row(align=True) + row = row.split(percentage=0.25) + row.prop(prefs, "kr_show_tools", text="Tools", icon='SEQ_SEQUENCER') + if prefs.kr_show_tools: + row = row.split(percentage=0.25) + row.prop(prefs, "kr_mini_ui", text="mini") + else: + row = row.split(percentage=0.25) + row.label(text = "") + + row = row.split(percentage=0.25) + row.label(text = "") + row = row.split(percentage=0.25) + row.label(text = "") + 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 == True: + row.prop(scn, "kr_auto_markers", text="") + + 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") + 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: # NOT prefs.kr_mini_ui: + + row=layout.row(align=True) + row.label("Snap:") + #row=layout.row(align=True) + 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=layout.row(align=True) + 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 + + row=layout.row() + row=layout.row(align=True) + split = row.split(percentage=0.99) + colR2 = split.column() + row1 = colR2.row(align=True) + row1.operator("sequencerextra.metacopy", icon="COPYDOWN", text="meta-copy") + row1.operator("sequencerextra.metapaste", icon='PASTEDOWN', text="paste-snap") + split = row.split(percentage=0.99) + colR3 = split.column() + colR3.operator('sequencerextra.meta_separate_trim', text='unMeta & Trim', icon='ALIGN') + + + + # IN /OUT TOOLS + row=layout.box() + split=row.split(percentage=0.7) + colR1 = split.column() + row1=colR1.row(align=True) + row1.operator("sequencerextra.sourcein", icon="MARKER_HLT", text="set IN") + row1.operator("sequencerextra.sourceout", icon='MARKER_HLT', text="set OUT") + colR3 = split.column() + colR3.operator("sequencerextra.setinout", icon="ARROW_LEFTRIGHT", text="selected") + + split=row.split(percentage=0.7) + colR1 = split.column() + row1=colR1.row(align=True) + if scn.kr_auto_markers == False: + row1.prop(scn, "kr_auto_markers", text="auto markers") + else: + row1.prop(scn, "kr_auto_markers", text="") + row1.prop(scn, "kr_in_marker") + row1.prop(scn, "kr_out_marker") + row1.active = scn.kr_auto_markers + colR3 = split.column() + colR3.operator("sequencerextra.triminout", icon="FULLSCREEN_EXIT", text="trim",emboss=True) + + + # UI extra actions + row=layout.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=layout.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 + # INFO boxes + # INFO boxes + if strip != 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 in {'MOVIE', 'SOUND'}: + row.prop(strip, "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() + row.prop(elem, "filename", text="File") # strip.elements[0] could be a fallback + 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") + if prefs.kr_show_trim: + if not isinstance(strip, bpy.types.EffectSequence): + row = layout.row(align=True) + row.label(text="hard:") + row.prop(strip, "animation_offset_start", text="Start") + row.prop(strip, "animation_offset_end", text="End") + row = layout.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) + + row = box.row(align=True) + + col = row.column() + col.prop(strip, "strobe") + col.prop(strip, "use_flip_x", text="flip X") + col.prop(strip, "use_flip_y", text="flip Y") + col.prop(strip, "use_reverse_frames", text="Backwards") + col.prop(strip, "use_deinterlace") + + col = row.column() + col.prop(strip, "blend_type", icon='COLOR', text = "") + + col.prop(strip, "color_saturation", text="Saturation") + col.prop(strip, "color_multiply", text="Multiply") + col.prop(strip, "use_float", text="Convert Float") + col.prop(strip, "alpha_mode") + + 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") + + + #sound type + else: + row = box.row(align=True) + row.prop(strip, "volume") + row.prop(strip, "mute", toggle=True, icon_only=True) + row.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") + + row.prop(strip, "show_waveform") + + row = box.row() + 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') + + + + + + + + + + + + + + + + + -- cgit v1.2.3