Update for API change: scene.cursor_location -> scene.cursor.location
[blender-addons.git] / sequencer_kinoraw_tools / operators_extra_actions.py
blob8a060acbc0bfe13390d2e1f4fabc5a4dd9244fd4
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 import bpy
20 import os
21 from bpy.types import Operator
22 from bpy.props import (
23 IntProperty,
24 FloatProperty,
25 EnumProperty,
26 BoolProperty,
28 from . import functions
31 # Skip one second
32 class Sequencer_Extra_FrameSkip(Operator):
33 bl_label = "Skip One Second"
34 bl_idname = "screenextra.frame_skip"
35 bl_description = "Skip through the Timeline by one-second increments"
36 bl_options = {'REGISTER', 'UNDO'}
38 back: BoolProperty(
39 name="Back",
40 default=False
43 def execute(self, context):
44 one_second = bpy.context.scene.render.fps
45 if self.back is True:
46 one_second *= -1
47 bpy.ops.screen.frame_offset(delta=one_second)
49 return {'FINISHED'}
52 # Trim timeline
53 class Sequencer_Extra_TrimTimeline(Operator):
54 bl_label = "Trim to Timeline Content"
55 bl_idname = "timeextra.trimtimeline"
56 bl_description = "Automatically set start and end frames"
57 bl_options = {'REGISTER', 'UNDO'}
59 @classmethod
60 def poll(self, context):
61 scn = context.scene
62 if scn and scn.sequence_editor:
63 return scn.sequence_editor.sequences
64 else:
65 return False
67 def execute(self, context):
68 scn = context.scene
69 seq = scn.sequence_editor
70 meta_level = len(seq.meta_stack)
71 if meta_level > 0:
72 seq = seq.meta_stack[meta_level - 1]
74 frame_start = 300000
75 frame_end = -300000
76 for i in seq.sequences:
77 try:
78 if i.frame_final_start < frame_start:
79 frame_start = i.frame_final_start
80 if i.frame_final_end > frame_end:
81 frame_end = i.frame_final_end - 1
82 except AttributeError:
83 pass
85 if frame_start != 300000:
86 scn.frame_start = frame_start
87 if frame_end != -300000:
88 scn.frame_end = frame_end
90 bpy.ops.sequencer.view_all()
92 return {'FINISHED'}
95 # Trim timeline to selection
96 class Sequencer_Extra_TrimTimelineToSelection(Operator):
97 bl_label = "Trim to Selection"
98 bl_idname = "timeextra.trimtimelinetoselection"
99 bl_description = "Set start and end frames to selection"
100 bl_options = {'REGISTER', 'UNDO'}
102 @classmethod
103 def poll(self, context):
104 scn = context.scene
105 if scn and scn.sequence_editor:
106 return scn.sequence_editor.sequences
107 else:
108 return False
110 def execute(self, context):
111 scn = context.scene
112 seq = scn.sequence_editor
113 meta_level = len(seq.meta_stack)
114 if meta_level > 0:
115 seq = seq.meta_stack[meta_level - 1]
117 frame_start = 300000
118 frame_end = -300000
119 for i in seq.sequences:
120 try:
121 if i.frame_final_start < frame_start and i.select is True:
122 frame_start = i.frame_final_start
123 if i.frame_final_end > frame_end and i.select is True:
124 frame_end = i.frame_final_end - 1
125 except AttributeError:
126 pass
128 if frame_start != 300000:
129 scn.frame_start = frame_start
130 if frame_end != -300000:
131 scn.frame_end = frame_end
133 bpy.ops.sequencer.view_selected()
134 return {'FINISHED'}
137 # Open image with editor and create movie clip strip
139 When a movie or image strip is selected, this operator creates a movieclip
140 or find the correspondent movieclip that already exists for this footage,
141 and add a VSE clip strip with same cuts the original strip has.
142 It can convert movie strips and image sequences, both with hard cuts or
143 soft cuts.
147 class Sequencer_Extra_CreateMovieclip(Operator):
148 bl_label = "Create a Movieclip from selected strip"
149 bl_idname = "sequencerextra.createmovieclip"
150 bl_description = "Create a Movieclip strip from a MOVIE or IMAGE strip"
152 @classmethod
153 def poll(self, context):
154 strip = functions.act_strip(context)
155 scn = context.scene
156 if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
157 return strip.type in ('MOVIE', 'IMAGE')
158 else:
159 return False
161 def execute(self, context):
162 strip = functions.act_strip(context)
163 scn = context.scene
165 if strip.type == 'MOVIE':
166 path = strip.filepath
167 data_exists = False
169 for i in bpy.data.movieclips:
170 if i.filepath == path:
171 data_exists = True
172 data = i
173 newstrip = None
174 if data_exists is False:
175 try:
176 data = bpy.data.movieclips.load(filepath=path)
177 newstrip = bpy.ops.sequencer.movieclip_strip_add(
178 replace_sel=True, overlap=False,
179 clip=data.name
181 newstrip = functions.act_strip(context)
182 newstrip.frame_start = strip.frame_start\
183 - strip.animation_offset_start
184 tin = strip.frame_offset_start + strip.frame_start
185 tout = tin + strip.frame_final_duration
186 # print(newstrip.frame_start, strip.frame_start, tin, tout)
187 functions.triminout(newstrip, tin, tout)
188 except:
189 self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
190 return {'CANCELLED'}
192 else:
193 try:
194 newstrip = bpy.ops.sequencer.movieclip_strip_add(
195 replace_sel=True, overlap=False,
196 clip=data.name
198 newstrip = functions.act_strip(context)
199 newstrip.frame_start = strip.frame_start\
200 - strip.animation_offset_start
201 # i need to declare the strip this way in order
202 # to get triminout() working
203 clip = bpy.context.scene.sequence_editor.sequences[
204 newstrip.name
206 # i cannot change these movie clip attributes via scripts
207 # but it works in the python console...
208 # clip.animation_offset_start = strip.animation.offset_start
209 # clip.animation_offset_end = strip.animation.offset_end
210 # clip.frame_final_duration = strip.frame_final_duration
211 tin = strip.frame_offset_start + strip.frame_start
212 tout = tin + strip.frame_final_duration
213 # print(newstrip.frame_start, strip.frame_start, tin, tout)
214 functions.triminout(clip, tin, tout)
215 except:
216 self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
217 return {'CANCELLED'}
219 elif strip.type == 'IMAGE':
220 # print("image")
221 base_dir = bpy.path.abspath(strip.directory)
222 scn.frame_current = strip.frame_start - strip.animation_offset_start
224 # searching for the first frame of the sequencer. This is mandatory
225 # for hard cutted sequence strips to be correctly converted,
226 # avoiding to create a new movie clip if not needed
227 filename = sorted(os.listdir(base_dir))[0]
228 path = os.path.join(base_dir, filename)
229 # print(path)
230 data_exists = False
231 for i in bpy.data.movieclips:
232 # print(i.filepath, path)
233 if i.filepath == path:
234 data_exists = True
235 data = i
236 # print(data_exists)
237 if data_exists is False:
238 try:
239 data = bpy.data.movieclips.load(filepath=path)
240 newstrip = bpy.ops.sequencer.movieclip_strip_add(
241 replace_sel=True, overlap=False,
242 clip=data.name
244 newstrip = functions.act_strip(context)
245 newstrip.frame_start = strip.frame_start\
246 - strip.animation_offset_start
247 clip = bpy.context.scene.sequence_editor.sequences[
248 newstrip.name
250 tin = strip.frame_offset_start + strip.frame_start
251 tout = tin + strip.frame_final_duration
252 # print(newstrip.frame_start, strip.frame_start, tin, tout)
253 functions.triminout(clip, tin, tout)
254 except:
255 self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
256 return {'CANCELLED'}
257 else:
258 try:
259 newstrip = bpy.ops.sequencer.movieclip_strip_add(
260 replace_sel=True, overlap=False,
261 clip=data.name
263 newstrip = functions.act_strip(context)
264 newstrip.frame_start = strip.frame_start\
265 - strip.animation_offset_start
266 # need to declare the strip this way in order
267 # to get triminout() working
268 clip = bpy.context.scene.sequence_editor.sequences[
269 newstrip.name
271 # cannot change this attributes via scripts...
272 # but it works in the python console...
273 # clip.animation_offset_start = strip.animation.offset_start
274 # clip.animation_offset_end = strip.animation.offset_end
275 # clip.frame_final_duration = strip.frame_final_duration
276 tin = strip.frame_offset_start + strip.frame_start
277 tout = tin + strip.frame_final_duration
278 # print(newstrip.frame_start, strip.frame_start, tin, tout)
279 functions.triminout(clip, tin, tout)
280 except:
281 self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
282 return {'CANCELLED'}
284 # show the new clip in a movie clip editor, if available.
285 if strip.type == 'MOVIE' or 'IMAGE':
286 for a in context.window.screen.areas:
287 if a.type == 'CLIP_EDITOR':
288 a.spaces[0].clip = data
290 return {'FINISHED'}
293 # Open image with editor
294 class Sequencer_Extra_Edit(Operator):
295 bl_label = "Open with Editor"
296 bl_idname = "sequencerextra.edit"
297 bl_description = "Open with Movie Clip or Image Editor"
299 @classmethod
300 def poll(self, context):
301 strip = functions.act_strip(context)
302 scn = context.scene
303 if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
304 return strip.type in ('MOVIE', 'IMAGE')
305 else:
306 return False
308 def execute(self, context):
309 strip = functions.act_strip(context)
310 scn = context.scene
311 data_exists = False
313 if strip.type == 'MOVIE':
314 path = strip.filepath
316 for i in bpy.data.movieclips:
317 if i.filepath == path:
318 data_exists = True
319 data = i
321 if data_exists is False:
322 try:
323 data = bpy.data.movieclips.load(filepath=path)
324 except:
325 self.report({'ERROR_INVALID_INPUT'}, "Error loading file")
326 return {'CANCELLED'}
328 elif strip.type == 'IMAGE':
329 base_dir = bpy.path.abspath(strip.directory)
330 strip_elem = strip.strip_elem_from_frame(scn.frame_current)
331 elem_name = strip_elem.filename
332 path = base_dir + elem_name
334 for i in bpy.data.images:
335 if i.filepath == path:
336 data_exists = True
337 data = i
339 if data_exists is False:
340 try:
341 data = bpy.data.images.load(filepath=path)
342 except:
343 self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
344 return {'CANCELLED'}
346 if strip.type == 'MOVIE':
347 for a in context.window.screen.areas:
348 if a.type == 'CLIP_EDITOR':
349 a.spaces[0].clip = data
350 elif strip.type == 'IMAGE':
351 for a in context.window.screen.areas:
352 if a.type == 'IMAGE_EDITOR':
353 a.spaces[0].image = data
355 return {'FINISHED'}
358 # Open image with external editor
359 class Sequencer_Extra_EditExternally(Operator):
360 bl_label = "Open with External Editor"
361 bl_idname = "sequencerextra.editexternally"
362 bl_description = "Open with the default external image editor"
364 @classmethod
365 def poll(self, context):
366 strip = functions.act_strip(context)
367 scn = context.scene
368 if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
369 return strip.type == 'IMAGE'
370 else:
371 return False
373 def execute(self, context):
374 strip = functions.act_strip(context)
375 scn = context.scene
376 base_dir = bpy.path.abspath(strip.directory)
377 strip_elem = strip.strip_elem_from_frame(scn.frame_current)
378 path = base_dir + strip_elem.filename
380 try:
381 bpy.ops.image.external_edit(filepath=path)
382 except:
383 self.report({'ERROR_INVALID_INPUT'},
384 "Please specify an Image Editor in Preferences > File")
385 return {'CANCELLED'}
387 return {'FINISHED'}
390 # File name to strip name
391 class Sequencer_Extra_FileNameToStripName(Operator):
392 bl_label = "File Name to Selected Strips Name"
393 bl_idname = "sequencerextra.striprename"
394 bl_description = "Set strip name to input file name"
395 bl_options = {'REGISTER', 'UNDO'}
397 @classmethod
398 def poll(self, context):
399 scn = context.scene
400 if scn and scn.sequence_editor:
401 return scn.sequence_editor.sequences
402 else:
403 return False
405 def execute(self, context):
406 scn = context.scene
407 seq = scn.sequence_editor
408 meta_level = len(seq.meta_stack)
409 if meta_level > 0:
410 seq = seq.meta_stack[meta_level - 1]
411 selection = False
412 for i in seq.sequences:
413 if i.select is True:
414 if i.type == 'IMAGE' and not i.mute:
415 selection = True
416 i.name = i.elements[0].filename
417 if (i.type == 'SOUND' or i.type == 'MOVIE') and not i.mute:
418 selection = True
419 i.name = bpy.path.display_name_from_filepath(i.filepath)
420 if selection is False:
421 self.report({'ERROR_INVALID_INPUT'},
422 "No image or movie strip selected")
423 return {'CANCELLED'}
424 return {'FINISHED'}
427 # Navigate up
428 class Sequencer_Extra_NavigateUp(Operator):
429 bl_label = "Navigate Up"
430 bl_idname = "sequencerextra.navigateup"
431 bl_description = "Move to Parent Timeline"
433 @classmethod
434 def poll(self, context):
435 try:
436 if context.scene.sequence_editor.meta_stack:
437 return True
438 return False
439 except:
440 return False
442 def execute(self, context):
443 if (functions.act_strip(context)):
444 strip = functions.act_strip(context)
445 seq_type = strip.type
446 if seq_type == 'META':
447 context.scene.sequence_editor.active_strip = None
449 bpy.ops.sequencer.meta_toggle()
450 return {'FINISHED'}
453 # Ripple delete
454 class Sequencer_Extra_RippleDelete(Operator):
455 bl_label = "Ripple Delete"
456 bl_idname = "sequencerextra.rippledelete"
457 bl_description = "Delete a strip and shift back following ones"
458 bl_options = {'REGISTER', 'UNDO'}
460 @classmethod
461 def poll(self, context):
462 scn = context.scene
463 if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
464 return True
465 else:
466 return False
468 def execute(self, context):
469 scn = context.scene
470 seq = scn.sequence_editor
471 meta_level = len(seq.meta_stack)
472 if meta_level > 0:
473 seq = seq.meta_stack[meta_level - 1]
474 # strip = functions.act_strip(context)
475 for strip in context.selected_editable_sequences:
476 cut_frame = strip.frame_final_start
477 next_edit = 300000
478 bpy.ops.sequencer.select_all(action='DESELECT')
479 strip.select = True
480 bpy.ops.sequencer.delete()
481 striplist = []
482 for i in seq.sequences:
483 try:
484 if (i.frame_final_start > cut_frame and
485 not i.mute):
486 if i.frame_final_start < next_edit:
487 next_edit = i.frame_final_start
488 if not i.mute:
489 striplist.append(i)
490 except AttributeError:
491 pass
493 if next_edit == 300000:
494 return {'FINISHED'}
495 ripple_length = next_edit - cut_frame
496 for i in range(len(striplist)):
497 str = striplist[i]
498 try:
499 if str.frame_final_start > cut_frame:
500 str.frame_start = str.frame_start - ripple_length
501 except AttributeError:
502 pass
503 bpy.ops.sequencer.reload()
504 return {'FINISHED'}
507 # Ripple cut
508 class Sequencer_Extra_RippleCut(Operator):
509 bl_label = "Ripple Cut"
510 bl_idname = "sequencerextra.ripplecut"
511 bl_description = "Move a strip to buffer and shift back following ones"
512 bl_options = {'REGISTER', 'UNDO'}
514 @classmethod
515 def poll(self, context):
516 scn = context.scene
517 if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
518 return True
519 else:
520 return False
522 def execute(self, context):
523 scn = context.scene
524 seq = scn.sequence_editor
525 meta_level = len(seq.meta_stack)
526 if meta_level > 0:
527 seq = seq.meta_stack[meta_level - 1]
528 strip = functions.act_strip(context)
529 bpy.ops.sequencer.select_all(action='DESELECT')
530 strip.select = True
531 temp_cf = scn.frame_current
532 scn.frame_current = strip.frame_final_start
533 bpy.ops.sequencer.copy()
534 scn.frame_current = temp_cf
536 bpy.ops.sequencerextra.rippledelete()
537 return {'FINISHED'}
540 # Insert
541 class Sequencer_Extra_Insert(Operator):
542 bl_label = "Insert"
543 bl_idname = "sequencerextra.insert"
544 bl_description = ("Move active strip to current frame and shift "
545 "forward following ones")
546 bl_options = {'REGISTER', 'UNDO'}
548 singlechannel: BoolProperty(
549 name="Single Channel",
550 default=False
553 @classmethod
554 def poll(self, context):
555 scn = context.scene
556 if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
557 return True
558 else:
559 return False
561 def execute(self, context):
562 scn = context.scene
563 seq = scn.sequence_editor
564 meta_level = len(seq.meta_stack)
565 if meta_level > 0:
566 seq = seq.meta_stack[meta_level - 1]
567 strip = functions.act_strip(context)
568 gap = strip.frame_final_duration
569 bpy.ops.sequencer.select_all(action='DESELECT')
570 current_frame = scn.frame_current
572 striplist = []
573 for i in seq.sequences:
574 try:
575 if (i.frame_final_start >= current_frame and
576 not i.mute):
577 if self.singlechannel is True:
578 if i.channel == strip.channel:
579 striplist.append(i)
580 else:
581 striplist.append(i)
582 except AttributeError:
583 pass
584 try:
585 bpy.ops.sequencerextra.selectcurrentframe('EXEC_DEFAULT',
586 mode='AFTER')
587 except:
588 self.report({'ERROR_INVALID_INPUT'}, "Execution Error, "
589 "check your Blender version")
590 return {'CANCELLED'}
592 for i in range(len(striplist)):
593 str = striplist[i]
594 try:
595 if str.select is True:
596 str.frame_start += gap
597 except AttributeError:
598 pass
599 try:
600 diff = current_frame - strip.frame_final_start
601 strip.frame_start += diff
602 except AttributeError:
603 pass
605 strip = functions.act_strip(context)
606 scn.frame_current += strip.frame_final_duration
607 bpy.ops.sequencer.reload()
609 return {'FINISHED'}
612 # Copy strip properties
613 class Sequencer_Extra_CopyProperties(Operator):
614 bl_label = "Copy Properties"
615 bl_idname = "sequencerextra.copyproperties"
616 bl_description = "Copy properties of active strip to selected strips"
617 bl_options = {'REGISTER', 'UNDO'}
619 prop: EnumProperty(
620 name="Property",
621 items=[
622 # common
623 ('name', 'Name', ''),
624 ('blend_alpha', 'Opacity', ''),
625 ('blend_type', 'Blend Mode', ''),
626 ('animation_offset', 'Input - Trim Duration', ''),
627 # non-sound
628 ('use_translation', 'Input - Image Offset', ''),
629 ('crop', 'Input - Image Crop', ''),
630 ('proxy', 'Proxy / Timecode', ''),
631 ('strobe', 'Filter - Strobe', ''),
632 ('color_multiply', 'Filter - Multiply', ''),
633 ('color_saturation', 'Filter - Saturation', ''),
634 ('deinterlace', 'Filter - De-Interlace', ''),
635 ('flip', 'Filter - Flip', ''),
636 ('float', 'Filter - Convert Float', ''),
637 ('alpha_mode', 'Filter - Alpha Mode', ''),
638 ('reverse', 'Filter - Backwards', ''),
639 # sound
640 ('pan', 'Sound - Pan', ''),
641 ('pitch', 'Sound - Pitch', ''),
642 ('volume', 'Sound - Volume', ''),
643 ('cache', 'Sound - Caching', ''),
644 # image
645 ('directory', 'Image - Directory', ''),
646 # movie
647 ('mpeg_preseek', 'Movie - MPEG Preseek', ''),
648 ('stream_index', 'Movie - Stream Index', ''),
649 # wipe
650 ('wipe', 'Effect - Wipe', ''),
651 # transform
652 ('transform', 'Effect - Transform', ''),
653 # color
654 ('color', 'Effect - Color', ''),
655 # speed
656 ('speed', 'Effect - Speed', ''),
657 # multicam
658 ('multicam_source', 'Effect - Multicam Source', ''),
659 # effect
660 ('effect_fader', 'Effect - Effect Fader', ''),
662 default='blend_alpha'
665 @classmethod
666 def poll(self, context):
667 scn = context.scene
668 if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
669 return True
670 else:
671 return False
673 def execute(self, context):
674 strip = functions.act_strip(context)
676 scn = context.scene
677 seq = scn.sequence_editor
678 meta_level = len(seq.meta_stack)
679 if meta_level > 0:
680 seq = seq.meta_stack[meta_level - 1]
682 for i in seq.sequences:
683 if (i.select is True and not i.mute):
684 try:
685 if self.prop == 'name':
686 i.name = strip.name
687 elif self.prop == 'blend_alpha':
688 i.blend_alpha = strip.blend_alpha
689 elif self.prop == 'blend_type':
690 i.blend_type = strip.blend_type
691 elif self.prop == 'animation_offset':
692 i.animation_offset_start = strip.animation_offset_start
693 i.animation_offset_end = strip.animation_offset_end
694 elif self.prop == 'use_translation':
695 i.use_translation = strip.use_translation
696 i.transform.offset_x = strip.transform.offset_x
697 i.transform.offset_y = strip.transform.offset_y
698 elif self.prop == 'crop':
699 i.use_crop = strip.use_crop
700 i.crop.min_x = strip.crop.min_x
701 i.crop.min_y = strip.crop.min_y
702 i.crop.max_x = strip.crop.max_x
703 i.crop.max_y = strip.crop.max_y
704 elif self.prop == 'proxy':
705 i.use_proxy = strip.use_proxy
706 p = strip.proxy.use_proxy_custom_directory # pep80
707 i.proxy.use_proxy_custom_directory = p
708 i.proxy.use_proxy_custom_file = strip.proxy.use_proxy_custom_file
709 i.proxy.build_100 = strip.proxy.build_100
710 i.proxy.build_25 = strip.proxy.build_25
711 i.proxy.build_50 = strip.proxy.build_50
712 i.proxy.build_75 = strip.proxy.build_75
713 i.proxy.directory = strip.proxy.directory
714 i.proxy.filepath = strip.proxy.filepath
715 i.proxy.quality = strip.proxy.quality
716 i.proxy.timecode = strip.proxy.timecode
717 i.proxy.use_overwrite = strip.proxy.use_overwrite
718 elif self.prop == 'strobe':
719 i.strobe = strip.strobe
720 elif self.prop == 'color_multiply':
721 i.color_multiply = strip.color_multiply
722 elif self.prop == 'color_saturation':
723 i.color_saturation = strip.color_saturation
724 elif self.prop == 'deinterlace':
725 i.use_deinterlace = strip.use_deinterlace
726 elif self.prop == 'flip':
727 i.use_flip_x = strip.use_flip_x
728 i.use_flip_y = strip.use_flip_y
729 elif self.prop == 'float':
730 i.use_float = strip.use_float
731 elif self.prop == 'alpha_mode':
732 i.alpha_mode = strip.alpha_mode
733 elif self.prop == 'reverse':
734 i.use_reverse_frames = strip.use_reverse_frames
735 elif self.prop == 'pan':
736 i.pan = strip.pan
737 elif self.prop == 'pitch':
738 i.pitch = strip.pitch
739 elif self.prop == 'volume':
740 i.volume = strip.volume
741 elif self.prop == 'cache':
742 i.use_memory_cache = strip.use_memory_cache
743 elif self.prop == 'directory':
744 i.directory = strip.directory
745 elif self.prop == 'mpeg_preseek':
746 i.mpeg_preseek = strip.mpeg_preseek
747 elif self.prop == 'stream_index':
748 i.stream_index = strip.stream_index
749 elif self.prop == 'wipe':
750 i.angle = strip.angle
751 i.blur_width = strip.blur_width
752 i.direction = strip.direction
753 i.transition_type = strip.transition_type
754 elif self.prop == 'transform':
755 i.interpolation = strip.interpolation
756 i.rotation_start = strip.rotation_start
757 i.use_uniform_scale = strip.use_uniform_scale
758 i.scale_start_x = strip.scale_start_x
759 i.scale_start_y = strip.scale_start_y
760 i.translation_unit = strip.translation_unit
761 i.translate_start_x = strip.translate_start_x
762 i.translate_start_y = strip.translate_start_y
763 elif self.prop == 'color':
764 i.color = strip.color
765 elif self.prop == 'speed':
766 i.use_default_fade = strip.use_default_fade
767 i.speed_factor = strip.speed_factor
768 i.use_as_speed = strip.use_as_speed
769 i.scale_to_length = strip.scale_to_length
770 i.multiply_speed = strip.multiply_speed
771 i.use_frame_blend = strip.use_frame_blend
772 elif self.prop == 'multicam_source':
773 i.multicam_source = strip.multicam_source
774 elif self.prop == 'effect_fader':
775 i.use_default_fade = strip.use_default_fade
776 i.effect_fader = strip.effect_fader
777 except:
778 pass
780 bpy.ops.sequencer.reload()
781 return {'FINISHED'}
784 # Fade in and out
785 class Sequencer_Extra_FadeInOut(Operator):
786 bl_idname = "sequencerextra.fadeinout"
787 bl_label = "Fade..."
788 bl_description = "Fade volume or opacity of active strip"
789 bl_options = {'REGISTER', 'UNDO'}
791 mode: EnumProperty(
792 name='Direction',
793 items=(
794 ('IN', "Fade In...", ""),
795 ('OUT', "Fade Out...", ""),
796 ('INOUT', "Fade In and Out...", "")),
797 default='IN',
800 fade_duration: IntProperty(
801 name='Duration',
802 description='Number of frames to fade',
803 min=1, max=250,
804 default=25)
805 fade_amount: FloatProperty(
806 name='Amount',
807 description='Maximum value of fade',
808 min=0.0,
809 max=100.0,
810 default=1.0)
812 @classmethod
813 def poll(cls, context):
814 scn = context.scene
815 if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
816 return True
817 else:
818 return False
820 def execute(self, context):
821 seq = context.scene.sequence_editor
822 scn = context.scene
823 strip = seq.active_strip
824 tmp_current_frame = context.scene.frame_current
826 if strip.type == 'SOUND':
827 if(self.mode) == 'OUT':
828 scn.frame_current = strip.frame_final_end - self.fade_duration
829 strip.volume = self.fade_amount
830 strip.keyframe_insert('volume')
831 scn.frame_current = strip.frame_final_end
832 strip.volume = 0
833 strip.keyframe_insert('volume')
834 elif(self.mode) == 'INOUT':
835 scn.frame_current = strip.frame_final_start
836 strip.volume = 0
837 strip.keyframe_insert('volume')
838 scn.frame_current += self.fade_duration
839 strip.volume = self.fade_amount
840 strip.keyframe_insert('volume')
841 scn.frame_current = strip.frame_final_end - self.fade_duration
842 strip.volume = self.fade_amount
843 strip.keyframe_insert('volume')
844 scn.frame_current = strip.frame_final_end
845 strip.volume = 0
846 strip.keyframe_insert('volume')
847 else:
848 scn.frame_current = strip.frame_final_start
849 strip.volume = 0
850 strip.keyframe_insert('volume')
851 scn.frame_current += self.fade_duration
852 strip.volume = self.fade_amount
853 strip.keyframe_insert('volume')
855 else:
856 if(self.mode) == 'OUT':
857 scn.frame_current = strip.frame_final_end - self.fade_duration
858 strip.blend_alpha = self.fade_amount
859 strip.keyframe_insert('blend_alpha')
860 scn.frame_current = strip.frame_final_end
861 strip.blend_alpha = 0
862 strip.keyframe_insert('blend_alpha')
863 elif(self.mode) == 'INOUT':
864 scn.frame_current = strip.frame_final_start
865 strip.blend_alpha = 0
866 strip.keyframe_insert('blend_alpha')
867 scn.frame_current += self.fade_duration
868 strip.blend_alpha = self.fade_amount
869 strip.keyframe_insert('blend_alpha')
870 scn.frame_current = strip.frame_final_end - self.fade_duration
871 strip.blend_alpha = self.fade_amount
872 strip.keyframe_insert('blend_alpha')
873 scn.frame_current = strip.frame_final_end
874 strip.blend_alpha = 0
875 strip.keyframe_insert('blend_alpha')
876 else:
877 scn.frame_current = strip.frame_final_start
878 strip.blend_alpha = 0
879 strip.keyframe_insert('blend_alpha')
880 scn.frame_current += self.fade_duration
881 strip.blend_alpha = self.fade_amount
882 strip.keyframe_insert('blend_alpha')
884 scn.frame_current = tmp_current_frame
886 scn.kr_default_fade_duration = self.fade_duration
887 scn.kr_default_fade_amount = self.fade_amount
888 return{'FINISHED'}
890 def invoke(self, context, event):
891 scn = context.scene
892 functions.initSceneProperties(context)
893 self.fade_duration = scn.kr_default_fade_duration
894 self.fade_amount = scn.kr_default_fade_amount
895 return context.window_manager.invoke_props_dialog(self)
898 # Extend to fill
899 class Sequencer_Extra_ExtendToFill(Operator):
900 bl_idname = "sequencerextra.extendtofill"
901 bl_label = "Extend to Fill"
902 bl_description = "Extend active strip forward to fill adjacent space"
903 bl_options = {'REGISTER', 'UNDO'}
905 @classmethod
906 def poll(cls, context):
907 scn = context.scene
908 if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
909 return True
910 else:
911 return False
913 def execute(self, context):
914 scn = context.scene
915 seq = scn.sequence_editor
916 meta_level = len(seq.meta_stack)
917 if meta_level > 0:
918 seq = seq.meta_stack[meta_level - 1]
919 strip = functions.act_strip(context)
920 chn = strip.channel
921 stf = strip.frame_final_end
922 enf = 300000
924 for i in seq.sequences:
925 ffs = i.frame_final_start
926 if (i.channel == chn and ffs > stf):
927 if ffs < enf:
928 enf = ffs
929 if enf == 300000 and stf < scn.frame_end:
930 enf = scn.frame_end
932 if enf == 300000 or enf == stf:
933 self.report({'ERROR_INVALID_INPUT'}, 'Unable to extend')
934 return {'CANCELLED'}
935 else:
936 strip.frame_final_end = enf
938 bpy.ops.sequencer.reload()
939 return {'FINISHED'}
942 # Place from file browser
943 class Sequencer_Extra_PlaceFromFileBrowser(Operator):
944 bl_label = "Place"
945 bl_idname = "sequencerextra.placefromfilebrowser"
946 bl_description = "Place or insert active file from File Browser"
947 bl_options = {'REGISTER', 'UNDO'}
949 insert: BoolProperty(
950 name="Insert",
951 default=False
954 def execute(self, context):
955 scn = context.scene
956 for a in context.window.screen.areas:
957 if a.type == 'FILE_BROWSER':
958 params = a.spaces[0].params
959 break
960 try:
961 params
962 except UnboundLocalError:
963 self.report({'ERROR_INVALID_INPUT'}, 'No visible File Browser')
964 return {'CANCELLED'}
966 if params.filename == '':
967 self.report({'ERROR_INVALID_INPUT'}, 'No file selected')
968 return {'CANCELLED'}
970 path = os.path.join(params.directory, params.filename)
971 frame = context.scene.frame_current
972 strip_type = functions.detect_strip_type(params.filename)
974 try:
975 if strip_type == 'IMAGE':
976 image_file = []
977 filename = {"name": params.filename}
978 image_file.append(filename)
979 f_in = scn.frame_current
980 f_out = f_in + scn.render.fps - 1
981 bpy.ops.sequencer.image_strip_add(files=image_file,
982 directory=params.directory, frame_start=f_in,
983 frame_end=f_out, relative_path=False)
984 elif strip_type == 'MOVIE':
985 bpy.ops.sequencer.movie_strip_add(filepath=path,
986 frame_start=frame, relative_path=False)
987 elif strip_type == 'SOUND':
988 bpy.ops.sequencer.sound_strip_add(filepath=path,
989 frame_start=frame, relative_path=False)
990 else:
991 self.report({'ERROR_INVALID_INPUT'}, 'Invalid file format')
992 return {'CANCELLED'}
993 except:
994 self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
995 return {'CANCELLED'}
997 if self.insert is True:
998 try:
999 striplist = []
1000 for i in bpy.context.selected_editable_sequences:
1001 if (i.select is True and i.type == "SOUND"):
1002 striplist.append(i)
1003 bpy.ops.sequencerextra.insert()
1004 if striplist[0]:
1005 striplist[0].frame_start = frame
1006 except:
1007 self.report({'ERROR_INVALID_INPUT'}, "Execution Error, "
1008 "check your Blender version")
1009 return {'CANCELLED'}
1010 else:
1011 strip = functions.act_strip(context)
1012 scn.frame_current += strip.frame_final_duration
1013 bpy.ops.sequencer.reload()
1015 return {'FINISHED'}
1018 # Select strips on same channel
1019 class Sequencer_Extra_SelectSameChannel(Operator):
1020 bl_label = "Select Strips on the Same Channel"
1021 bl_idname = "sequencerextra.selectsamechannel"
1022 bl_description = "Select strips on the same channel as active one"
1023 bl_options = {'REGISTER', 'UNDO'}
1025 @classmethod
1026 def poll(self, context):
1027 scn = context.scene
1028 if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
1029 return True
1030 else:
1031 return False
1033 def execute(self, context):
1034 scn = context.scene
1035 seq = scn.sequence_editor
1036 meta_level = len(seq.meta_stack)
1037 if meta_level > 0:
1038 seq = seq.meta_stack[meta_level - 1]
1039 bpy.ops.sequencer.select_active_side(side="LEFT")
1040 bpy.ops.sequencer.select_active_side(side="RIGHT")
1042 return {'FINISHED'}
1045 # Current-frame-aware select
1046 class Sequencer_Extra_SelectCurrentFrame(Operator):
1047 bl_label = "Current-Frame-Aware Select"
1048 bl_idname = "sequencerextra.selectcurrentframe"
1049 bl_description = "Select strips according to current frame"
1050 bl_options = {'REGISTER', 'UNDO'}
1052 mode: EnumProperty(
1053 name='Mode',
1054 items=(
1055 ('BEFORE', 'Before Current Frame', ''),
1056 ('AFTER', 'After Current Frame', ''),
1057 ('ON', 'On Current Frame', '')),
1058 default='BEFORE',
1061 @classmethod
1062 def poll(self, context):
1063 scn = context.scene
1064 if scn and scn.sequence_editor:
1065 return scn.sequence_editor.sequences
1066 else:
1067 return False
1069 def execute(self, context):
1070 mode = self.mode
1071 scn = context.scene
1072 seq = scn.sequence_editor
1073 cf = scn.frame_current
1074 meta_level = len(seq.meta_stack)
1075 if meta_level > 0:
1076 seq = seq.meta_stack[meta_level - 1]
1078 if mode == 'AFTER':
1079 for i in seq.sequences:
1080 try:
1081 if (i.frame_final_start >= cf and not i.mute):
1082 i.select = True
1083 except AttributeError:
1084 pass
1085 elif mode == 'ON':
1086 for i in seq.sequences:
1087 try:
1088 if (i.frame_final_start <= cf and
1089 i.frame_final_end > cf and
1090 not i.mute):
1091 i.select = True
1092 except AttributeError:
1093 pass
1094 else:
1095 for i in seq.sequences:
1096 try:
1097 if (i.frame_final_end < cf and not i.mute):
1098 i.select = True
1099 except AttributeError:
1100 pass
1102 return {'FINISHED'}
1105 # Select by type
1106 class Sequencer_Extra_SelectAllByType(Operator):
1107 bl_label = "All by Type"
1108 bl_idname = "sequencerextra.select_all_by_type"
1109 bl_description = "Select all the strips of the same type"
1110 bl_options = {'REGISTER', 'UNDO'}
1112 type: EnumProperty(
1113 name="Strip Type",
1114 items=(
1115 ('ACTIVE', 'Same as Active Strip', ''),
1116 ('IMAGE', 'Image', ''),
1117 ('META', 'Meta', ''),
1118 ('SCENE', 'Scene', ''),
1119 ('MOVIE', 'Movie', ''),
1120 ('SOUND', 'Sound', ''),
1121 ('TRANSFORM', 'Transform', ''),
1122 ('COLOR', 'Color', '')),
1123 default='ACTIVE',
1126 @classmethod
1127 def poll(self, context):
1128 scn = context.scene
1129 if scn and scn.sequence_editor:
1130 return scn.sequence_editor.sequences
1131 else:
1132 return False
1134 def execute(self, context):
1135 strip_type = self.type
1136 scn = context.scene
1137 seq = scn.sequence_editor
1138 meta_level = len(seq.meta_stack)
1139 if meta_level > 0:
1140 seq = seq.meta_stack[meta_level - 1]
1141 active_strip = functions.act_strip(context)
1142 if strip_type == 'ACTIVE':
1143 if active_strip is None:
1144 self.report({'ERROR_INVALID_INPUT'},
1145 'No active strip')
1146 return {'CANCELLED'}
1147 strip_type = active_strip.type
1149 striplist = []
1150 for i in seq.sequences:
1151 try:
1152 if (i.type == strip_type and not i.mute):
1153 striplist.append(i)
1154 except AttributeError:
1155 pass
1156 for i in range(len(striplist)):
1157 str = striplist[i]
1158 try:
1159 str.select = True
1160 except AttributeError:
1161 pass
1163 return {'FINISHED'}
1166 # Open in movie clip editor from file browser
1167 class Clip_Extra_OpenFromFileBrowser(Operator):
1168 bl_label = "Open from File Browser"
1169 bl_idname = "clipextra.openfromfilebrowser"
1170 bl_description = "Load a Movie or Image Sequence from File Browser"
1171 bl_options = {'REGISTER', 'UNDO'}
1173 def execute(self, context):
1174 for a in context.window.screen.areas:
1175 if a.type == 'FILE_BROWSER':
1176 params = a.spaces[0].params
1177 break
1178 try:
1179 params
1180 except:
1181 self.report({'ERROR_INVALID_INPUT'}, 'No visible File Browser')
1182 return {'CANCELLED'}
1184 if params.filename == '':
1185 self.report({'ERROR_INVALID_INPUT'}, 'No file selected')
1186 return {'CANCELLED'}
1188 path = params.directory + params.filename
1189 strip_type = functions.detect_strip_type(params.filename)
1190 data_exists = False
1192 if strip_type in ('MOVIE', 'IMAGE'):
1193 for i in bpy.data.movieclips:
1194 if i.filepath == path:
1195 data_exists = True
1196 data = i
1198 if data_exists is False:
1199 try:
1200 data = bpy.data.movieclips.load(filepath=path)
1201 except:
1202 self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
1203 return {'CANCELLED'}
1204 else:
1205 self.report({'ERROR_INVALID_INPUT'}, 'Invalid file format')
1206 return {'CANCELLED'}
1208 for a in context.window.screen.areas:
1209 if a.type == 'CLIP_EDITOR':
1210 a.spaces[0].clip = data
1212 return {'FINISHED'}
1215 # Open in movie clip editor from sequencer
1216 class Clip_Extra_OpenActiveStrip(Operator):
1217 bl_label = "Open Active Strip"
1218 bl_idname = "clipextra.openactivestrip"
1219 bl_description = "Load a Movie or Image Sequence from Sequence Editor"
1220 bl_options = {'REGISTER', 'UNDO'}
1222 @classmethod
1223 def poll(cls, context):
1224 scn = context.scene
1225 strip = functions.act_strip(context)
1226 if scn and scn.sequence_editor and scn.sequence_editor.active_strip:
1227 return strip.type in ('MOVIE', 'IMAGE')
1228 else:
1229 return False
1231 def execute(self, context):
1232 strip = functions.act_strip(context)
1233 data_exists = False
1235 if strip.type == 'MOVIE':
1236 path = strip.filepath
1237 elif strip.type == 'IMAGE':
1238 base_dir = bpy.path.relpath(strip.directory)
1239 filename = strip.elements[0].filename
1240 path = base_dir + '/' + filename
1241 else:
1242 self.report({'ERROR_INVALID_INPUT'}, 'Invalid file format')
1243 return {'CANCELLED'}
1245 for i in bpy.data.movieclips:
1246 if i.filepath == path:
1247 data_exists = True
1248 data = i
1249 if data_exists is False:
1250 try:
1251 data = bpy.data.movieclips.load(filepath=path)
1252 except:
1253 self.report({'ERROR_INVALID_INPUT'}, 'Error loading file')
1254 return {'CANCELLED'}
1256 for a in context.window.screen.areas:
1257 if a.type == 'CLIP_EDITOR':
1258 a.spaces[0].clip = data
1260 return {'FINISHED'}
1263 # Jog / Shuttle
1264 class Sequencer_Extra_JogShuttle(Operator):
1265 bl_label = "Jog/Shuttle"
1266 bl_idname = "sequencerextra.jogshuttle"
1267 bl_description = ("Jog through the current sequence\n"
1268 "Left Mouse button to confirm, Right mouse\Esc to cancel")
1270 def execute(self, context):
1271 scn = context.scene
1272 start_frame = scn.frame_start
1273 end_frame = scn.frame_end
1274 duration = end_frame - start_frame
1275 diff = self.x - self.init_x
1276 diff /= 5
1277 diff = int(diff)
1278 extended_frame = diff + (self.init_current_frame - start_frame)
1279 looped_frame = extended_frame % (duration + 1)
1280 target_frame = start_frame + looped_frame
1281 context.scene.frame_current = target_frame
1283 def modal(self, context, event):
1284 if event.type == 'MOUSEMOVE':
1285 self.x = event.mouse_x
1286 self.execute(context)
1287 elif event.type == 'LEFTMOUSE':
1288 return {'FINISHED'}
1289 elif event.type in ('RIGHTMOUSE', 'ESC'):
1290 return {'CANCELLED'}
1292 return {'RUNNING_MODAL'}
1294 def invoke(self, context, event):
1295 scn = context.scene
1296 self.x = event.mouse_x
1297 self.init_x = self.x
1298 self.init_current_frame = scn.frame_current
1299 self.execute(context)
1300 context.window_manager.modal_handler_add(self)
1302 return {'RUNNING_MODAL'}