diff options
author | Nathan Lovato <nathan@gdquest.com> | 2019-09-05 18:22:37 +0300 |
---|---|---|
committer | Nathan Lovato <nathan@gdquest.com> | 2019-09-05 18:22:52 +0300 |
commit | 61d48c0a4be0ab8f71e6e1d35f0aa99c77fcfd33 (patch) | |
tree | f64d0ea88789184f45112b489598990d549788f2 /power_sequencer/operators/import_local_footage.py | |
parent | da5a1175e30c347fbce05e49e2f5f895be30bd5b (diff) |
Add the VSE addon Power Sequencer
Diffstat (limited to 'power_sequencer/operators/import_local_footage.py')
-rw-r--r-- | power_sequencer/operators/import_local_footage.py | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/power_sequencer/operators/import_local_footage.py b/power_sequencer/operators/import_local_footage.py new file mode 100644 index 00000000..8c8ca1ec --- /dev/null +++ b/power_sequencer/operators/import_local_footage.py @@ -0,0 +1,268 @@ +# +# Copyright (C) 2016-2019 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors +# +# This file is part of Power Sequencer. +# +# Power Sequencer 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. +# +# Power Sequencer 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 Power Sequencer. If +# not, see <https://www.gnu.org/licenses/>. +# +import json +import os +import re +from operator import attrgetter + +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)] + print(files_to_import) + 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, root), 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_text = 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 |