├── .gitignore ├── README.md └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enhanced Blender VSE Import, Export & Sequence Menu 2 | Enhanced UX and features for the file import and export of the Blender Video Sequence Editor 3 | 4 | ## Features 5 | * Add Sequencer menu 6 | * Colleced VSE file operations. 7 | * Add Export menu. 8 | * Add Import menu. 9 | * Import all supported filetypes in one File Browser. 10 | * Import sorting. 11 | * Import into channel. 12 | * Playhead and append insert methods. 13 | * All the default import options and more. 14 | * Import text as Text strip. 15 | * Etc. 16 | 17 | https://user-images.githubusercontent.com/1322593/227909044-2dc394a1-57f8-406e-a46e-a92d1a58c50f.mp4 18 | 19 | ## Menus 20 | 21 | ![image](https://user-images.githubusercontent.com/1322593/228251975-a1b27421-c195-4079-99fb-d6690b1bf19e.png) ![image](https://user-images.githubusercontent.com/1322593/229376211-eb1487b4-6d0a-4039-a3fa-999a4b72d9ce.png) 22 | 23 | ![image](https://user-images.githubusercontent.com/1322593/228249669-544779d8-c5d7-481d-a212-1bad0940b8eb.png) 24 | 25 | ## Acknowledgements 26 | 27 | Import class by Meta Person https://github.com/metaperson/blender_vse/blob/main/tube/20210924%231/addon_import_strips.py 28 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "Sequence File Menu", 3 | "author": "tintwotin, Meta Person", 4 | "version": (1, 0), 5 | "blender": (3, 4, 0), 6 | "location": "Video Sequence Editor > Menu > Sequence", 7 | "description": "File Operations for the Sequencer", 8 | "category": "Sequencer", 9 | } 10 | 11 | import bpy, os, re 12 | from bpy_extras.io_utils import ExportHelper 13 | from bpy_extras.io_utils import ImportHelper 14 | from bpy.props import StringProperty, BoolProperty, EnumProperty 15 | from bpy.types import Operator 16 | from pathlib import Path 17 | 18 | # Not exposed: 19 | # RENDER_PT_format_presets, RENDER_PT_ffmpeg_presets, RENDER_PT_format, 20 | # RENDER_PT_stereoscopy, RENDER_PT_stamp, RENDER_PT_stamp_note, RENDER_PT_stamp_burn 21 | from bl_ui.properties_output import RENDER_PT_encoding 22 | from bl_ui.properties_output import RENDER_PT_encoding_video 23 | from bl_ui.properties_output import RENDER_PT_encoding_audio 24 | from bl_ui.properties_output import RENDER_PT_frame_range 25 | from bl_ui.properties_output import RENDER_PT_time_stretching 26 | from bl_ui.properties_output import RENDER_PT_post_processing 27 | from bl_ui.properties_output import RENDER_PT_output_color_management 28 | from bl_ui.properties_output import RENDER_PT_output_views 29 | 30 | audio_extensions = [ 31 | ".wav", 32 | ".ogg", 33 | ".oga", 34 | ".mp3", 35 | ".mp2", 36 | ".ac3", 37 | ".aac", 38 | ".flac", 39 | ".wma", 40 | ".eac3", 41 | ".aif", 42 | ".aiff", 43 | ".m4a", 44 | ".mka" 45 | ] 46 | 47 | image_extensions = [ 48 | ".png", 49 | ".tga", 50 | ".bmp", 51 | ".jpg", 52 | ".jpeg", 53 | ".sgi", 54 | ".rgb", 55 | ".rgba", 56 | ".tif", 57 | ".tiff", 58 | ".tx", 59 | ".jp2", 60 | ".j2c", 61 | ".hdr", 62 | ".dds", 63 | ".dpx", 64 | ".cin", 65 | ".exr", 66 | ".psd", 67 | ".pdd", 68 | ".psb", 69 | ".webp", 70 | ".psd", 71 | ".pdd", 72 | ".psb" 73 | ] 74 | 75 | video_extensions = [ 76 | ".avi", 77 | ".flc", 78 | ".mov", 79 | ".movie", 80 | ".mp4", 81 | ".m4v", 82 | ".m2v", 83 | ".m2t", 84 | ".m2ts", 85 | ".mts", 86 | ".ts", 87 | ".mv", 88 | ".avs", 89 | ".wmv", 90 | ".ogv", 91 | #".ogg", 92 | ".r3d", 93 | ".dv", 94 | ".mpeg", 95 | ".mpg", 96 | ".mpg2", 97 | ".vob", 98 | ".mkv", 99 | ".flv", 100 | ".divx", 101 | ".xvid", 102 | ".mxf", 103 | ".gif", 104 | ".webm" 105 | ] 106 | 107 | text_extensions = [ 108 | ".txt", 109 | ] 110 | 111 | valid_extensions = image_extensions+video_extensions+audio_extensions+text_extensions 112 | delimiter = ";*" 113 | valid_extensions_str = delimiter.join(valid_extensions) 114 | 115 | def export_file(context, filepath): 116 | dirname = os.path.dirname(filepath) 117 | filename = os.path.splitext(filepath)[0] 118 | context.scene.render.filepath = os.path.join(dirname, filename) 119 | bpy.ops.render.opengl(animation=True, sequencer=True) 120 | return {"FINISHED"} 121 | 122 | 123 | class SEQUENCER_PT_export_browser(Operator, ExportHelper): 124 | """Export the sequence with audio""" 125 | 126 | bl_idname = "sequencer.export" 127 | bl_label = "Export" 128 | filename_ext = "." 129 | # use_filter_folder = True 130 | use_filter_movie = True 131 | use_filter_image = True 132 | filter_glob: StringProperty(default=valid_extensions_str, options={"HIDDEN"}, maxlen=255) 133 | auto_range: bpy.props.BoolProperty(name='Auto Range', description='Set the range to include the imported strips', default=True) 134 | 135 | def execute(self, context): 136 | 137 | if self.auto_range: 138 | selection = bpy.context.selected_sequences 139 | bpy.ops.sequencer.select_all(action='SELECT') 140 | bpy.ops.sequencer.set_range_to_strips() 141 | bpy.ops.sequencer.select_all(action='DESELECT') 142 | for s in selection: s.select = True 143 | 144 | return export_file(context, self.filepath) 145 | 146 | # def draw(self, context): 147 | # pass 148 | 149 | 150 | class SEQUENCER_PT_export(bpy.types.Panel): 151 | bl_space_type = "FILE_BROWSER" 152 | bl_region_type = "TOOL_PROPS" 153 | bl_label = "Export Options" 154 | bl_parent_id = "FILE_PT_operator" 155 | 156 | @classmethod 157 | def poll(cls, context): 158 | sfile = context.space_data 159 | operator = sfile.active_operator 160 | return operator.bl_idname == "SEQUENCER_OT_export" 161 | 162 | def invoke(self, context, event): 163 | context.scene.render.use_sequencer = True 164 | 165 | def draw(self, context): 166 | pass 167 | 168 | 169 | class SEQUENCER_PT_export_image(bpy.types.Panel): 170 | bl_space_type = "FILE_BROWSER" 171 | bl_region_type = "TOOL_PROPS" 172 | bl_label = "Output" 173 | bl_parent_id = "SEQUENCER_PT_export" 174 | 175 | def draw(self, context): 176 | layout = self.layout 177 | rd = context.scene.render 178 | image_settings = rd.image_settings 179 | 180 | layout.use_property_split = True 181 | 182 | col = layout.column(heading="Saving") 183 | col.prop(rd, "use_file_extension") 184 | col.prop(rd, "use_render_cache") 185 | 186 | layout.template_image_settings(image_settings, color_management=False) 187 | 188 | if not rd.is_movie_format: 189 | col = layout.column(heading="Image Sequence") 190 | col.prop(rd, "use_overwrite") 191 | col.prop(rd, "use_placeholder") 192 | 193 | 194 | class SEQUENCER_PT_export_color_management(bpy.types.Panel): 195 | bl_space_type = "FILE_BROWSER" 196 | bl_region_type = "TOOL_PROPS" 197 | bl_label = "Color Management" 198 | bl_parent_id = "SEQUENCER_PT_export_image" 199 | bl_options = {"DEFAULT_CLOSED"} 200 | 201 | def draw(self, context): 202 | layout = self.layout 203 | RENDER_PT_output_color_management.draw(self, context) 204 | 205 | 206 | class SEQUENCER_PT_export_encoding(bpy.types.Panel): 207 | bl_space_type = "FILE_BROWSER" 208 | bl_region_type = "TOOL_PROPS" 209 | bl_label = "Encoding" 210 | bl_parent_id = "SEQUENCER_PT_export_image" 211 | 212 | @classmethod 213 | def poll(cls, context): 214 | rd = context.scene.render 215 | return rd.image_settings.file_format in {"FFMPEG", "XVID", "H264", "THEORA"} 216 | layout = self.layout 217 | 218 | def draw(self, context): 219 | RENDER_PT_encoding.draw(self, context) 220 | 221 | 222 | class SEQUENCER_PT_export_encoding_video(bpy.types.Panel): 223 | bl_space_type = "FILE_BROWSER" 224 | bl_region_type = "TOOL_PROPS" 225 | bl_label = "Video" 226 | bl_parent_id = "SEQUENCER_PT_export_encoding" 227 | 228 | def draw(self, context): 229 | layout = self.layout 230 | layout.use_property_split = True 231 | rd = context.scene.render 232 | if rd.image_settings.file_format in {"FFMPEG", "XVID", "H264", "THEORA"}: 233 | ffmpeg = context.scene.render.ffmpeg 234 | 235 | needs_codec = ffmpeg.format in { 236 | "AVI", 237 | "QUICKTIME", 238 | "MKV", 239 | "OGG", 240 | "MPEG4", 241 | "WEBM", 242 | } 243 | if needs_codec: 244 | layout.prop(ffmpeg, "codec") 245 | if needs_codec and ffmpeg.codec == "NONE": 246 | return 247 | if ffmpeg.codec == "DNXHD": 248 | layout.prop(ffmpeg, "use_lossless_output") 249 | # Output quality 250 | use_crf = needs_codec and ffmpeg.codec in {"H264", "MPEG4", "WEBM", "AV1"} 251 | if use_crf: 252 | layout.prop(ffmpeg, "constant_rate_factor") 253 | # Encoding speed 254 | layout.prop(ffmpeg, "ffmpeg_preset") 255 | # I-frames 256 | layout.prop(ffmpeg, "gopsize") 257 | # B-Frames 258 | row = layout.row(align=True, heading="Max B-frames") 259 | row.prop(ffmpeg, "use_max_b_frames", text="") 260 | sub = row.row(align=True) 261 | sub.active = ffmpeg.use_max_b_frames 262 | sub.prop(ffmpeg, "max_b_frames", text="") 263 | 264 | if not use_crf or ffmpeg.constant_rate_factor == "NONE": 265 | col = layout.column() 266 | 267 | sub = col.column(align=True) 268 | sub.prop(ffmpeg, "video_bitrate") 269 | sub.prop(ffmpeg, "minrate", text="Minimum") 270 | sub.prop(ffmpeg, "maxrate", text="Maximum") 271 | 272 | col.prop(ffmpeg, "buffersize", text="Buffer") 273 | 274 | col.separator() 275 | 276 | col.prop(ffmpeg, "muxrate", text="Mux Rate") 277 | col.prop(ffmpeg, "packetsize", text="Mux Packet Size") 278 | 279 | 280 | class SEQUENCER_PT_export_audio(bpy.types.Panel): 281 | bl_space_type = "FILE_BROWSER" 282 | bl_region_type = "TOOL_PROPS" 283 | bl_label = "Audio" 284 | bl_parent_id = "SEQUENCER_PT_export_encoding" 285 | 286 | @classmethod 287 | def poll(cls, context): 288 | rd = context.scene.render 289 | return rd.image_settings.file_format in {"FFMPEG", "XVID", "H264", "THEORA"} 290 | layout = self.layout 291 | 292 | def draw(self, context): 293 | RENDER_PT_encoding_audio.draw(self, context) 294 | 295 | 296 | class SEQUENCER_PT_export_frame_range(bpy.types.Panel): 297 | bl_space_type = "FILE_BROWSER" 298 | bl_region_type = "TOOL_PROPS" 299 | bl_label = "Frame Range" 300 | bl_parent_id = "SEQUENCER_PT_export" 301 | bl_options = {"DEFAULT_CLOSED"} 302 | 303 | def draw(self, context): 304 | RENDER_PT_frame_range.draw(self, context) 305 | 306 | 307 | class SEQUENCER_PT_export_time_stretching(bpy.types.Panel): 308 | bl_space_type = "FILE_BROWSER" 309 | bl_region_type = "TOOL_PROPS" 310 | bl_label = "Time Stretching" 311 | bl_parent_id = "SEQUENCER_PT_export_frame_range" 312 | bl_options = {"DEFAULT_CLOSED"} 313 | 314 | def draw(self, context): 315 | RENDER_PT_time_stretching.draw(self, context) 316 | 317 | 318 | class SEQUENCER_PT_export_post_processing(bpy.types.Panel): 319 | bl_space_type = "FILE_BROWSER" 320 | bl_region_type = "TOOL_PROPS" 321 | bl_label = "Post Processing" 322 | bl_parent_id = "SEQUENCER_PT_export" 323 | bl_options = {"DEFAULT_CLOSED"} 324 | 325 | def draw(self, context): 326 | RENDER_PT_post_processing.draw(self, context) 327 | 328 | 329 | def menu_func_export(self, context): 330 | # self.layout.operator( 331 | # SEQUENCER_PT_export_frame_range.bl_idname, text="Sequencer Export" 332 | # ) 333 | self.layout.operator("sequencer.export", text="Sequencer Export") 334 | 335 | 336 | def find_first_empty_channel(start_frame, end_frame): 337 | for ch in range(1, len(bpy.context.scene.sequence_editor.sequences_all) + 1): 338 | for seq in bpy.context.scene.sequence_editor.sequences_all: 339 | if ( 340 | seq.channel == ch 341 | and seq.frame_final_start < end_frame 342 | and (seq.frame_final_start + seq.frame_final_duration) > start_frame 343 | ): 344 | break 345 | else: 346 | return ch 347 | return 1 348 | 349 | 350 | # Class started by Meta Person: https://github.com/metaperson/blender_vse/blob/main/tube/20210924%231/addon_import_strips.py 351 | class SEQUENCER_OT_import_strips(bpy.types.Operator, ImportHelper): 352 | bl_description = 'Import multiple strips of mixed types' 353 | bl_idname = 'sequencer.import_strips' 354 | bl_label = 'Import' 355 | 356 | files: bpy.props.CollectionProperty(name='Import Strips', type=bpy.types.OperatorFileListElement) 357 | 358 | insert_method: bpy.props.EnumProperty(name='Insert Position', 359 | description='Insert method', 360 | items=[('PLAYHEAD', 'Playhead', 'Insert at playhead position'), 361 | ('APPEND', 'Append', 'Append strips to the end of sequence')], 362 | default='PLAYHEAD') 363 | 364 | channel: bpy.props.IntProperty(name='Channel', description='Assign channel to put strips', default=1, min=1) 365 | 366 | relative_path: bpy.props.BoolProperty(name='Relative Path', description='Use a relative path', default=False) 367 | 368 | fit_method: bpy.props.EnumProperty(name='Scale', 369 | description='Scale fit method', 370 | items=[('FIT', 'Scale to Fit', 'Scale image to fit within the canvas'), 371 | ('FILL', 'Scale to Fill', 'Scale image to completely fill the canvas'), 372 | ('STRETCH', 'Stretch to Fill', 'Stretch image to fill the canvas'), 373 | ('ORIGINAL', 'Use Original Size', 'Keep image at its original size')], 374 | default='FIT') 375 | 376 | order_by: bpy.props.EnumProperty(name='Import Order', 377 | description='Strips are sorted by this order', 378 | items=[('PICK', 'Pick', 'No Sort, as selected order'), 379 | ('CREATE_TIME', 'File Create Time', 'Sort order by the file created time'), 380 | ('FILE_NAME', 'File Name', 'Sort order by the file name'), 381 | ('FILE_SIZE', 'File Size', 'Sort order by the file size')], 382 | default='FILE_NAME') 383 | 384 | reversed_order: bpy.props.BoolProperty(name='Reversed Order', description='Reversed order for sorting', default=False) 385 | 386 | image_strip_length: bpy.props.IntProperty(name='Image Duration', description='Image strip length', default=75, min=1) 387 | 388 | use_placeholders: bpy.props.BoolProperty(name='Image Placeholders', description='Use placeholders for missing images', default=True) 389 | 390 | adjust_playback_rate: bpy.props.BoolProperty(name='Adjust Strip FPS', description='Play at normal speed regardless of scene FPS', default=True) 391 | 392 | use_framerate: bpy.props.BoolProperty(name='Override Scene FPS', description='Set the scene frame rate according to framerate of Movie strip', default=True) 393 | 394 | set_view_transform: bpy.props.BoolProperty(name='Scene Color Space', description='Set the scene colorspace view transform according to strip', default=True) 395 | 396 | add_sound: bpy.props.BoolProperty(name='Import Sound', description='Import sound for Movie strips', default=True) 397 | mono: bpy.props.BoolProperty(name='Mix to Mono', description='Mix sound channels to mono', default=False) 398 | cache: bpy.props.BoolProperty(name='Cache Sound', description='Cache the sound of Sound strips', default=False) 399 | 400 | auto_range: bpy.props.BoolProperty(name='Adjust Range', description='Set the range to include the imported strips', default=True) 401 | 402 | filter_glob: StringProperty(default=valid_extensions_str, options={"HIDDEN"}, maxlen=255) 403 | 404 | @classmethod 405 | def poll(cls, context): 406 | if not bpy.ops.sequencer.movie_strip_add.poll(): 407 | return False 408 | if not bpy.ops.sequencer.sound_strip_add.poll(): 409 | return False 410 | if not bpy.ops.sequencer.image_strip_add.poll(): 411 | return False 412 | return True 413 | 414 | def execute(self, context): 415 | #print('ImportHelper.files : {}'.format(self.files)) 416 | #print('file path : {}'.format(self.filepath)) 417 | #print('Sort by order : {}'.format(self.order_by)) 418 | 419 | strip_dirname = os.path.dirname(self.filepath) 420 | #strip_dirname = Path(os.path.realpath(strip_dirname)) 421 | if not self.relative_path: 422 | strip_dirname = os.path.abspath(strip_dirname) 423 | strip_files = self.files 424 | 425 | if self.order_by == 'PICK': 426 | if self.reversed_order: 427 | strip_files = reversed(strip_files) 428 | else: 429 | strip_files = list(strip_files) 430 | elif self.order_by == 'CREATE_TIME': 431 | strip_files = sorted(strip_files, key=lambda x: os.path.getctime(os.path.join(strip_dirname, x.name)), reverse=self.reversed_order) 432 | elif self.order_by == 'FILE_NAME': 433 | strip_files = sorted(strip_files, key=lambda x: x.name, reverse=self.reversed_order) 434 | elif self.order_by == 'FILE_SIZE': 435 | strip_files = sorted(strip_files, key=lambda x: os.path.getsize(os.path.join(strip_dirname, x.name)), reverse=self.reversed_order) 436 | else: 437 | return {'CANCELLED'} 438 | 439 | # for strip_file in strip_files: 440 | # print(strip_file) 441 | 442 | count_movie = 0 443 | count_sound = 0 444 | count_image = 0 445 | count_text = 0 446 | 447 | imported_strips = [] 448 | new_strip = "" 449 | empty_channel = find_first_empty_channel(0, 10000000000) 450 | for strip_file in strip_files: 451 | strip_ext = os.path.splitext(strip_file.name)[1].lower() 452 | # print(strip_dirname, strip_file.name, strip_ext) 453 | 454 | strip_path = os.path.join(strip_dirname, strip_file.name) 455 | if not self.relative_path: 456 | strip_path = os.path.abspath(strip_path) 457 | 458 | if self.insert_method == "APPEND": 459 | frame_start = max([seq.frame_final_end for seq in context.sequences] or [0]) 460 | channel = self.channel 461 | elif self.insert_method == "PLAYHEAD": 462 | if any(seq.frame_final_end for seq in imported_strips): 463 | frame_start = max([seq.frame_final_end for seq in imported_strips] or [context.scene.frame_current]) 464 | channel = max(self.channel, empty_channel) 465 | else: 466 | frame_start = context.scene.frame_current 467 | channel = self.channel 468 | 469 | if strip_ext in video_extensions: 470 | bpy.ops.sequencer.movie_strip_add(filepath=strip_path, 471 | frame_start=frame_start, 472 | channel=channel, 473 | fit_method=self.fit_method, 474 | set_view_transform=self.set_view_transform, 475 | adjust_playback_rate=self.adjust_playback_rate, 476 | sound=self.add_sound, 477 | use_framerate = self.use_framerate, 478 | relative_path=self.relative_path, 479 | overlap = False, 480 | ) 481 | count_movie += 1 482 | new_strip = context.scene.sequence_editor.active_strip 483 | elif strip_ext in audio_extensions: 484 | strip_path = os.path.join(strip_dirname, strip_file.name) 485 | bpy.ops.sequencer.sound_strip_add(filepath=strip_path, 486 | frame_start=frame_start, 487 | channel=channel, 488 | cache=self.cache, 489 | mono=self.mono, 490 | relative_path=self.relative_path, 491 | overlap = False, 492 | ) 493 | count_sound += 1 494 | new_strip = context.scene.sequence_editor.active_strip 495 | elif strip_ext in image_extensions: 496 | bpy.ops.sequencer.image_strip_add(directory=strip_dirname + '\\', files=[{"name":strip_file.name}], 497 | show_multiview=False, 498 | frame_start=frame_start, 499 | frame_end=frame_start+self.image_strip_length, 500 | channel=channel, 501 | fit_method=self.fit_method, 502 | set_view_transform=self.set_view_transform, 503 | use_placeholders=self.use_placeholders, 504 | relative_path=self.relative_path, 505 | overlap = False, 506 | ) 507 | count_image += 1 508 | new_strip = context.scene.sequence_editor.active_strip 509 | new_strip.frame_final_duration = self.image_strip_length 510 | elif strip_ext in text_extensions: 511 | bpy.ops.sequencer.effect_strip_add(type='TEXT', 512 | frame_start=frame_start, 513 | frame_end=frame_start+self.image_strip_length, 514 | channel=channel, 515 | ) 516 | count_text += 1 517 | new_strip = context.scene.sequence_editor.active_strip 518 | new_strip.frame_final_duration = self.image_strip_length 519 | strip_path = os.path.join(strip_dirname, strip_file.name) 520 | strip_path = os.path.abspath(strip_path) 521 | with open(strip_path) as f: 522 | content = f.read() 523 | new_strip.text = content 524 | if new_strip != "": 525 | imported_strips.append(new_strip) 526 | new_strip = "" 527 | 528 | if self.auto_range: 529 | selection = bpy.context.selected_sequences 530 | bpy.ops.sequencer.select_all(action='SELECT') 531 | bpy.ops.sequencer.set_range_to_strips() 532 | bpy.ops.sequencer.select_all(action='DESELECT') 533 | for s in selection: s.select = True 534 | 535 | self.report({'INFO'}, 'Imported Movie[{}], Sound[{}], Image[{}], Text[{}], Total[{}]'.format(count_movie, 536 | count_sound, 537 | count_image, 538 | count_text, 539 | count_movie + count_sound + count_image + count_image)) 540 | 541 | return {'FINISHED'} 542 | 543 | set_default_filter_settings: bool = True 544 | def draw(self, context): 545 | if self.set_default_filter_settings: 546 | context.space_data.params.use_filter = True # enable Filter 547 | context.space_data.params.use_filter_movie = True # set movie filter as default 548 | context.space_data.params.use_filter_image = True # set movie filter as default 549 | context.space_data.params.use_filter_sound = True # set movie filter as default 550 | self.set_default_filter_settings = False 551 | pass 552 | 553 | 554 | class SEQUENCER_PT_import_strips(bpy.types.Panel): 555 | bl_space_type = 'FILE_BROWSER' 556 | bl_region_type = 'TOOL_PROPS' 557 | bl_label = "Import" 558 | bl_parent_id = "FILE_PT_operator" 559 | 560 | @classmethod 561 | def poll(cls, context): 562 | sfile = context.space_data 563 | operator = sfile.active_operator 564 | 565 | return operator.bl_idname == "SEQUENCER_OT_import_strips" 566 | 567 | set_default_filter_settings: bool = True 568 | def draw(self, context): 569 | if self.set_default_filter_settings: 570 | context.space_data.params.use_filter = True # enable Filter 571 | context.space_data.params.use_filter_movie = True # set movie filter as default 572 | context.space_data.params.use_filter_image = True # set movie filter as default 573 | context.space_data.params.use_filter_sound = True # set movie filter as default 574 | self.set_default_filter_settings = False 575 | layout = self.layout 576 | layout.use_property_split = True 577 | layout.use_property_decorate = False # No animation. 578 | prefs = context.preferences 579 | system = prefs.system 580 | 581 | sfile = context.space_data 582 | operator = sfile.active_operator 583 | 584 | # General 585 | box = layout.box() 586 | box = box.column(align=True) 587 | box.prop(operator, "insert_method") 588 | box.prop(operator, "channel") 589 | box.prop(operator, "relative_path") 590 | box.prop(operator, "auto_range") 591 | box.separator() 592 | 593 | #box = layout.box() 594 | box = box.column(align=True) 595 | box.prop(operator, "order_by") 596 | box.prop(operator, "reversed_order") 597 | box.separator() 598 | 599 | #box = layout.box() 600 | box = box.column(align=True) 601 | box.prop(operator, "fit_method") 602 | row = box.row(align=True, heading="Override") 603 | row.prop(operator, "set_view_transform") 604 | 605 | # Video 606 | box = layout.box() 607 | box = box.column(align=True) 608 | box.label(text="Movie", icon="FILE_MOVIE") 609 | box.prop(system, "sequencer_proxy_setup") 610 | box.prop(operator, "adjust_playback_rate") 611 | box.prop(operator, "use_framerate") 612 | box.prop(operator, "add_sound") 613 | 614 | # Image 615 | box = layout.box() 616 | box = box.column(align=True) 617 | box.label(text="Image/Sequence", icon="IMAGE_DATA") 618 | box.prop(operator, "image_strip_length") 619 | #box.prop(operator, "use_sequence") 620 | box.prop(operator, "use_placeholders") 621 | 622 | # Sound 623 | box = layout.box() 624 | box = box.column(align=True) 625 | box.label(text="Sound", icon="FILE_SOUND") 626 | box.prop(operator, "mono") 627 | box.prop(operator, "cache") 628 | 629 | 630 | class SEQUENCER_MT_sequence(bpy.types.Menu): 631 | bl_idname = "SEQUENCER_MT_sequence" 632 | bl_label = "Sequence" 633 | 634 | def draw(self, context): 635 | layout = self.layout 636 | 637 | layout.operator_context = "INVOKE_REGION_WIN" 638 | layout.operator("sequencer.refresh_all", text="Refresh All") 639 | layout.operator_context = "INVOKE_DEFAULT" 640 | 641 | layout.separator() 642 | layout.operator("sequencer.import_strips", text="Import Media") 643 | 644 | layout.separator() 645 | props = layout.operator("sequencer.export", text="Export Sequence") 646 | layout.operator("sound.mixdown", text="Export Audio") 647 | layout.operator("sequencer.export_subtitles", text="Export Subtitles") 648 | 649 | layout.separator() 650 | layout.operator("render.opengl", text="Render Frame").sequencer = True 651 | 652 | 653 | def prepend_sequence_menu(self, context): 654 | self.layout.menu("SEQUENCER_MT_sequence") 655 | 656 | 657 | classes = ( 658 | SEQUENCER_PT_export, 659 | SEQUENCER_PT_export_image, 660 | SEQUENCER_PT_export_color_management, 661 | SEQUENCER_PT_export_encoding, 662 | SEQUENCER_PT_export_encoding_video, 663 | SEQUENCER_PT_export_audio, 664 | SEQUENCER_PT_export_frame_range, 665 | SEQUENCER_PT_export_time_stretching, 666 | SEQUENCER_PT_export_post_processing, 667 | SEQUENCER_PT_export_browser, 668 | SEQUENCER_OT_import_strips, 669 | SEQUENCER_PT_import_strips, 670 | SEQUENCER_MT_sequence, 671 | ) 672 | 673 | 674 | def register(): 675 | for cls in classes: 676 | bpy.utils.register_class(cls) 677 | bpy.types.TOPBAR_MT_file_export.append(menu_func_export) 678 | bpy.types.SEQUENCER_MT_editor_menus.prepend(prepend_sequence_menu) 679 | 680 | 681 | def unregister(): 682 | for cls in classes: 683 | bpy.utils.unregister_class(cls) 684 | bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) 685 | bpy.types.SEQUENCER_MT_editor_menus.remove(prepend_sequence_menu) 686 | 687 | 688 | if __name__ == "__main__": 689 | register() 690 | 691 | # test call 692 | #bpy.ops.sequencer.export('INVOKE_DEFAULT') 693 | --------------------------------------------------------------------------------