# SPDX-License-Identifier: GPL-3.0-or-later # Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors import json import os import re import bpy from .utils.functions import convert_duration_to_frames from .utils.doc import doc_brief, doc_description, doc_idname, doc_name from ..addon_preferences import get_preferences from .utils.global_settings import ( Extensions, EXTENSIONS_ALL, EXTENSIONS_AUDIO, EXTENSIONS_IMG, EXTENSIONS_VIDEO, ) class POWER_SEQUENCER_OT_import_local_footage(bpy.types.Operator): """*brief* Imports video, images, and audio from the project folder Finds and imports all valid video, audio files, and pictures in the blend file's folder and sub-folders, ignoring folders named BL_proxy. If you set it in the add-on preferences, it also sets imported sequences to use proxies. See `Preferences -> Add-ons -> Blender Power Sequencer -> Proxy` """ doc = { "name": doc_name(__qualname__), "demo": "", "description": doc_description(__doc__), "shortcuts": [ ( {"type": "I", "value": "PRESS", "ctrl": True, "shift": True}, {"keep_audio": True}, "Import Local Footage", ) ], "keymap": "Sequencer", } bl_idname = doc_idname(__qualname__) bl_label = doc["name"] bl_description = doc_brief(doc["description"]) bl_options = {"REGISTER", "UNDO"} keep_audio: bpy.props.BoolProperty( name="Keep Audio from Video Files", description=("If False, the audio that comes with video files will" " not be imported"), default=True, ) img_length: bpy.props.FloatProperty( name="Image strip Length", description="Controls the duration of the imported image strip in seconds", default=3.0, min=1.0, ) img_padding: bpy.props.FloatProperty( name="Image Padding", description="Padding added between imported image strips in seconds", default=1.0, min=0.0, ) sequencer_area = None directory = "" @classmethod def poll(cls, context): return bpy.data.is_saved def execute(self, context): self.sequencer_area = self.get_sequencer_area(context) self.directory = os.path.split(bpy.data.filepath)[0] filepaths = self.find_local_footage_files() files_to_import = [ os.path.join(self.directory, f) for f in self.find_new_files_to_import(filepaths) ] if not files_to_import: self.report({"INFO"}, "No new files to import found") return {"FINISHED"} bpy.ops.screen.animation_cancel(restore_frame=True) audio = self.import_audios( context, [f for f in files_to_import if f.lower().endswith(EXTENSIONS_AUDIO)] ) video = self.import_videos( context, [f for f in files_to_import if f.lower().endswith(EXTENSIONS_VIDEO)] ) img = self.import_imgs( context, [f for f in files_to_import if f.lower().endswith(EXTENSIONS_IMG)] ) bpy.data.texts["POWER_SEQUENCER_IMPORTS"].from_string(json.dumps(filepaths)) for s in audio: s.show_waveform = True imported = audio + video + img for s in imported: s.select = True self.set_selected_strips_proxies(context) self.report({"INFO"}, "Imported {!s} strips from newly found files.".format(len(imported))) return {"FINISHED"} def get_sequencer_area(self, context): """ Returns the sequencer area to use as a context override in some operators """ sequencer_area = None for window in context.window_manager.windows: for area in window.screen.areas: if not area.type == "SEQUENCE_EDITOR": continue sequencer_area = { "window": window, "screen": window.screen, "area": area, "scene": context.scene, } return sequencer_area def find_local_footage_files(self, ignored_directories=["BL_proxy"]): """ Returns a list of relative filepaths in all subdirectories of the `self.directory` for all valid files that can be imported in the Sequencer """ files_list = [] for root, dirs, files in os.walk(self.directory): for directory in ignored_directories: if directory in dirs: dirs.remove(directory) files = [f for f in sorted(files) if f.lower().endswith(EXTENSIONS_ALL)] files = map(lambda name: os.path.join(root, name), files) files = map(lambda path: os.path.relpath(path, self.directory), files) files_list.extend(list(files)) return files_list def create_import_text_block(self, name): """ Creates a new text data block that contains an empty json list, renames it to `name` and returns it """ re.compile(r"^Text.[0-9]{3}$") bpy.ops.text.new() # Find the newly created text file's identifier ids = [text.name for text in bpy.data.texts if text.name.startswith("Text")] id = max(ids) text_file = bpy.data.texts[id] text_file.name = name text_file.from_string("[]") return text_file def find_new_files_to_import(self, filepaths): """ Gets and optionally creates the list of already imported files in this project Returns a list of paths from filepaths that are not in the imported text file """ text_file = ( bpy.data.texts.get("POWER_SEQUENCER_IMPORTS") if "POWER_SEQUENCER_IMPORTS" in bpy.data.texts.keys() else self.create_import_text_block("POWER_SEQUENCER_IMPORTS") ) imported_files = json.loads(text_file.as_string()) files_to_import = [p for p in filepaths if p not in imported_files] return files_to_import def import_videos(self, context, videos_filepaths): """ Imports a list of files using movie_strip_add Returns the list of imported sequences """ frame = context.scene.frame_current context.window_manager.progress_begin(0, len(videos_filepaths)) imported = [] for index, f in enumerate(videos_filepaths): is_first_import = index == 0 bpy.ops.sequencer.movie_strip_add( self.sequencer_area, filepath=f, frame_start=frame, sound=self.keep_audio, use_framerate=is_first_import, ) imported.extend(context.selected_sequences) frame = context.selected_sequences[0].frame_final_end context.window_manager.progress_update(index) context.window_manager.progress_end() return imported def import_audios(self, context, audio_filepaths): """ Imports audio files as sound strips from a list of absolute file paths Returns the list of newly imported audio files """ frame = context.scene.frame_current imported = [] for f in audio_filepaths: bpy.ops.sequencer.sound_strip_add(self.sequencer_area, filepath=f, frame_start=frame) imported.extend(context.selected_sequences) frame = context.selected_sequences[0].frame_final_end return imported def import_imgs(self, context, img_filepaths): frame = context.scene.frame_current strip_length = convert_duration_to_frames(context, self.img_length) strip_padding = convert_duration_to_frames(context, self.img_padding) new_sequences = [] for f in img_filepaths: head, tail = os.path.split(f) bpy.ops.sequencer.image_strip_add( self.sequencer_area, directory=head, files=[{"name": tail}], frame_start=frame, frame_end=frame + strip_length, ) frame += strip_length + strip_padding new_sequences.extend(context.selected_sequences) return new_sequences def set_selected_strips_proxies(self, context): proxy_sizes = ["25", "50", "75", "100"] use_proxy = False prefs = get_preferences(context) for size in proxy_sizes: if hasattr(prefs, "proxy_" + size): use_proxy = True break if not use_proxy: return for s in [s for s in context.selected_sequences if s.type in ["MOVIE", "IMAGE"]]: s.use_proxy = True s.proxy.build_25 = prefs.proxy_25 s.proxy.build_50 = prefs.proxy_50 s.proxy.build_75 = prefs.proxy_75 s.proxy.build_100 = prefs.proxy_100