From c75c5469f9f070c66d98cbac132620de72f90ad3 Mon Sep 17 00:00:00 2001 From: Clement Ducarteron Date: Thu, 16 Mar 2023 18:32:17 +0100 Subject: [PATCH] import edl, import movie, import audio --- constants.py | 10 +++ operators/operators.py | 170 ++++++++++++++++++++++++++++++++++++----- panels.py | 2 +- properties.py | 31 +++++++- sequencer_utils.py | 137 ++++++++++++++++++++++++++------- 5 files changed, 298 insertions(+), 52 deletions(-) diff --git a/constants.py b/constants.py index e95639e..b45aa4c 100644 --- a/constants.py +++ b/constants.py @@ -5,6 +5,16 @@ MODULE_DIR = Path(__file__).parent TRACKERS_DIR = MODULE_DIR / 'resources' / 'trackers' TRACKERS = [] PROJECTS = [] + +EDITS = [('NONE', 'None', '', 0)] +MOVIES = [('NONE', 'None', '', 0)] +SOUNDS = [('NONE', 'None', '', 0)] + +EDIT_SUFFIXES = ['.xml', '.edl'] +MOVIE_SUFFIXES = ['.mov', '.mp4'] +SOUND_SUFFIXES = ['.mp3', '.aaf', '.flac'] + + # TRACKER_URL = os.environ.get("TRACKER_URL", "") # TRACKER_LOGIN = os.environ.get("TRACKER_LOGIN", "") # TRACKER_PASSWORD = os.environ.get("TRACKER_PASSWORD", "") \ No newline at end of file diff --git a/operators/operators.py b/operators/operators.py index 24cfeac..33cc937 100644 --- a/operators/operators.py +++ b/operators/operators.py @@ -17,10 +17,21 @@ from bpy.types import ( Operator, ) from pathlib import Path +from vse_toolbox.constants import ( + EDITS, + MOVIES, + SOUNDS, + EDIT_SUFFIXES, + MOVIE_SUFFIXES, + SOUND_SUFFIXES, +) from vse_toolbox.sequencer_utils import ( + clean_sequencer, get_shot_sequence, get_strips, - import_edl, + import_edit, + import_movie, + import_sound, rename_strips, render_strips, set_channels, @@ -44,10 +55,55 @@ class VSETB_OT_export_csv(Operator): return {"CANCELLED"} +class VSETB_OT_auto_select_files(Operator): + bl_idname = "import.auto_select_files" + bl_label = "Auto Select" + bl_description = "Auto Select Files" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return True + + def get_items(self, items=[]): + if not items: + return [('NONE', 'None', '', 0)] + return [(e, e, '', i) for i, e in enumerate(sorted(items))] + + def execute(self, context): + params = context.space_data.params + directory = Path(params.directory.decode()) + + EDITS.clear() + MOVIES.clear() + SOUNDS.clear() + + edits = [] + movies = [] + sounds = [] + + for file_entry in directory.glob('*'): + if file_entry.is_dir(): + continue + + if file_entry.suffix in EDIT_SUFFIXES: + edits.append(file_entry.name) + elif file_entry.suffix in MOVIE_SUFFIXES: + movies.append(file_entry.name) + elif file_entry.suffix in SOUND_SUFFIXES: + sounds.append(file_entry.name) + + EDITS.extend(self.get_items(items=edits)) + MOVIES.extend(self.get_items(items=movies)) + SOUNDS.extend(self.get_items(items=sounds)) + + return {'FINISHED'} + + class VSETB_OT_import(Operator): bl_idname = "sequencer.import" - bl_label = "Set Scene" - bl_description = "Set Scene for Breakdown" + bl_label = "Import" + bl_description = "Import Edit" bl_options = {"REGISTER", "UNDO"} directory : StringProperty(subtype='DIR_PATH') @@ -58,29 +114,103 @@ class VSETB_OT_import(Operator): subtype='FILE_PATH', ) files : CollectionProperty(type=bpy.types.OperatorFileListElement) - filter_glob: StringProperty( - default='*.edl;*.mov;*.wav', - options={'HIDDEN'} + + clean_sequencer : BoolProperty( + name="Clean Sequencer", + default=False, + description="Clean all existing strips in sequencer", ) + import_edit : BoolProperty(name='', default=True) + edit: EnumProperty(name='', items=lambda s, c: EDITS) + + import_movie : BoolProperty(name='', default=False) + movie: EnumProperty(name='', items=lambda s, c: MOVIES) + + import_sound : BoolProperty(name='', default=False) + sound: EnumProperty(name='', items=lambda s, c: SOUNDS) + @classmethod def poll(cls, context): return True + def draw(self, context): + scn = context.scene + settings = get_settings() + + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + col = layout.column(align=True) + col.operator('import.auto_select_files', text='Auto Select') + + row = self.layout.row(heading="Import Edit", align=True) + row.prop(self, 'import_edit') + sub = row.row() + sub.active = self.import_edit + sub.prop(self, 'edit') + + row = self.layout.row(heading="Import Movie", align=True) + row.prop(self, 'import_movie') + sub = row.row() + sub.active = self.import_movie + sub.prop(self, 'movie') + + row = self.layout.row(heading="Import Sound", align=True) + row.prop(self, 'import_sound') + sub = row.row() + sub.active = self.import_sound + sub.prop(self, 'sound') + + col = layout.column() + col.separator() + col.prop(self, 'clean_sequencer') + def invoke(self, context, event): context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} def execute(self, context): otio = install_module('opentimelineio') - for filepath in self.files: - filepath = Path(self.directory, filepath.name) - if filepath.suffix == '.edl': - edl = import_edl(filepath, adapter="cmx_3600") - print('edl: ', edl) - return {"CANCELLED"} + edit_filepath = Path(self.directory, self.edit) + if not edit_filepath.exists(): + self.import_edit = False + movie_filepath = Path(self.directory, self.movie) + if not movie_filepath.exists(): + self.import_movie = False + + sound_filepath = Path(self.directory, self.sound) + if not sound_filepath.exists(): + self.import_sound = False + + if self.clean_sequencer: + clean_sequencer( + edit=self.import_edit, + movie=self.import_movie, + sound=self.import_sound, + ) + + if self.import_edit: + print(f'[>.] Loading Edit from: {str(edit_filepath)}') + import_edit(edit_filepath, adapter="cmx_3600") + + if self.import_movie: + print(f'[>.] Loading Movie from: {str(movie_filepath)}') + import_movie(movie_filepath) + + if self.import_sound: + print(f'[>.] Loading Audio from: {str(sound_filepath)}') + import_sound(sound_filepath) + elif not self.import_sound and self.import_movie: + print(f'[>.] Loading Audio from Movie: {str(movie_filepath)}') + import_sound(movie_filepath) + + context.scene.sequence_editor.sequences.update() + + return {"FINISHED"} class VSETB_OT_load_projects(Operator): @@ -111,7 +241,7 @@ class VSETB_OT_load_projects(Operator): episode.name = episode_data['name'] episode.id = episode_data['id'] - return {'FINISHED'} + return {'FINISHED'} class VSETB_OT_new_episode(Operator): @@ -202,7 +332,8 @@ class VSETB_OT_rename(Operator): scn = context.scene settings = get_settings() - col = self.layout + layout = self.layout + col = layout.column() col.use_property_split = True col.prop(self, 'template') col.prop(self, 'start_number') @@ -245,15 +376,17 @@ class VSETB_OT_render(Operator): scn = context.scene settings = get_settings() - col = self.layout + layout = self.layout + col = layout.column() col.use_property_split = True col.use_property_decorate = False - col.prop(settings, 'channels', text='Channel') + col.prop(settings, 'channel', text='Channel') col.prop(self, 'selected_only') def execute(self, context): - scn = context.scene - strips = get_strips(channel=self.channel_name, selected_only=self.selected_only) + scn = context.scene + settings = get_settings() + strips = get_strips(channel=settings.channel, selected_only=self.selected_only) render_strips(strips) @@ -288,6 +421,7 @@ class VSETB_OT_set_scene(Operator): classes=( + VSETB_OT_auto_select_files, VSETB_OT_export_csv, VSETB_OT_import, VSETB_OT_load_projects, diff --git a/panels.py b/panels.py index acab8fc..5f6dc82 100644 --- a/panels.py +++ b/panels.py @@ -50,7 +50,7 @@ class VSETB_PT_main(VSETB_main, Panel): # TODO FAIRE DES VRAIS OPS row = col.row(align=True) - row.operator('sequencer.import', text='Import Edit', icon='IMPORT') + row.operator('sequencer.import', text='Import', icon='IMPORT') row.operator('sequencer.export_csv', text='Export', icon='EXPORT') op = col.operator('sequencer.strips_render', text='Render Strips', icon='RENDER_ANIMATION') diff --git a/properties.py b/properties.py index dade7e6..105272a 100644 --- a/properties.py +++ b/properties.py @@ -11,7 +11,7 @@ from bpy.props import ( PointerProperty, StringProperty, ) -from bpy.types import PropertyGroup +from bpy.types import PropertyGroup, UIList from pprint import pprint as pp from vse_toolbox.bl_utils import get_addon_prefs, get_settings from vse_toolbox.constants import TRACKERS @@ -69,11 +69,12 @@ class Project(PropertyGroup): name="Episode Name", default="e{index:03d}") shot_template : StringProperty( - name="Shot Name", default="{episode}_sh{index:04d}") + name="Shot Name", default="{episode}sh{index:04d}") episode_name : EnumProperty(items=get_episodes_items) episodes : CollectionProperty(type=Episode) + #FIXME Trouver une solution pour mettre des method dans les CollectionProperty class Projects(PropertyGroup): pass @@ -83,6 +84,27 @@ class Projects(PropertyGroup): # return self.get(settings.project_name) +class VSETB_UL_channels(UIList): + """Demo UIList.""" + + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + + items = bpy.types.GPTRACER_OT_add_property.__annotations__['enum'].keywords['items'] + + enum_item = next((i for i in items if i[0]==item.name), None) + if not enum_item: + return + + layout.label(text=enum_item[1], icon=enum_item[-2]) + + settings = data.settings + prop = item.name + if '.' in prop: + settings, prop = prop.split('.') + settings = getattr(data.settings, settings) + + layout.prop(settings, prop, text='') + class VSETB_PGT_settings(PropertyGroup): _projects = [] @@ -91,7 +113,7 @@ class VSETB_PGT_settings(PropertyGroup): tracker_name : EnumProperty(items=get_tracker_items) toogle_prefs : BoolProperty(description='Toogle VSE ToolBox Preferences') - channels : EnumProperty( + channel : EnumProperty( items=[ ('AUDIO', 'Audio', '', 0), ('MOVIE', 'Movie', '', 1), @@ -117,11 +139,12 @@ class VSETB_PGT_settings(PropertyGroup): if project: return project.episodes.get(project.episode_name) + classes=( Episode, Project, - Projects, VSETB_PGT_settings, + # VSETB_UL_channels, ) diff --git a/sequencer_utils.py b/sequencer_utils.py index fa2f60f..f3b7506 100644 --- a/sequencer_utils.py +++ b/sequencer_utils.py @@ -10,7 +10,7 @@ def get_strips(channel=0, selected_only=False): scn = bpy.context.scene if isinstance(channel, str): - channel = scn.sequence_editor.channels.keys().index(channel) + channel = get_channel(channel) strips = [s for s in scn.sequence_editor.sequences_all if s.channel==channel] @@ -19,6 +19,16 @@ def get_strips(channel=0, selected_only=False): return sorted(strips, key=lambda x : x.frame_final_start) +def get_channel(name): + scn = bpy.context.scene + + channel_id = 0 + channel = scn.sequence_editor.channels.get(name) + if channel: + channel_id = scn.sequence_editor.channels.keys().index(name) + + return channel_id + def get_shot_sequence(shot): sequences = get_strips(channel='Sequences') return next((s.name for s in sequences if s.frame_final_start<=shot.frame_final_start.] read_from_file Failed. Using read_from_string method.") data = edl.read_text(encoding='latin-1') timeline = otio.adapters.read_from_string( data, adapter, rate=scn.render.fps, ignore_timecode_mismatch=True) @@ -87,46 +114,98 @@ def import_edl(filepath, adapter="cmx_3600"): scn.frame_start = ( 0 if timeline.global_start_time is None else timeline.global_start_time ) - scn.frame_end = otio.opentime.to_frames(timeline.duration()) - 1 + # scn.frame_end = otio.opentime.to_frames(timeline.duration()) - for i, track in enumerate(timeline.tracks, 1): - for child in track.each_child(shallow_search=False): + for track in timeline.tracks: + for child in track.each_child(shallow_search=True): + + # FIXME Exclude Gaps for now. Gaps are Transitions, Blank Spaces... + # if type(child) != Clip: + if not isinstance(child, Clip): + continue + + # FIXME Exclude Audio for now + if any(child.name.endswith(ext) for ext in ('.wav', '.mp3')): + channel = get_channel('Audio') + continue + + channel = get_channel('Shots') frame_start = otio.opentime.to_frames( child.range_in_parent().start_time) - frame_end = otio.opentime.to_frames( - child.range_in_parent().duration) - 1 + frame_end = frame_start + otio.opentime.to_frames( + child.range_in_parent().duration) + try: strip = sequences.new_effect( - name='', + name=child.name, type='COLOR', - channel=i, + channel=channel, frame_start=frame_start, frame_end=frame_end, ) + strip.blend_alpha = 0.0 + except Exception as e: print('e: ', e) continue - """ - t = otio.adapters.read_from_file(edl, rate=C.scene.render.fps) - t.video_tracks() # Video Tracks du fichiers. C'est une liste. - Parcourir toutes les video_tracks ? - Trouver comment fait Felix David - """ - print('timeline: ', timeline) - + scn.frame_end = frame_end-1 return timeline -""" -def create_strip_effect(name, strip_type, channel, frame_start, frame_end): - strip = scn.sequence_editor.sequences.new_effect( - name=name, - type=strip_type, - channel=channel, - frame_start=frame_start, - frame_end=frame_end, +def import_movie(filepath): + scn = bpy.context.scene + + res_x = scn.render.resolution_x + res_y = scn.render.resolution_y + + strip = scn.sequence_editor.sequences.new_movie( + name=filepath.stem, + filepath=str(filepath), + channel=get_channel('Movie'), + frame_start=scn.frame_start ) + elem = strip.strip_elem_from_frame(scn.frame_current) + src_width, src_height = elem.orig_width, elem.orig_height + + if src_width != res_x: + strip.transform.scale_x = (res_x / src_width) + if src_height != res_y: + strip.transform.scale_y = (res_y / src_height) + + if bpy.data.is_saved: + strip.filepath = bpy.path.relpath(str(filepath)) + return strip -""" \ No newline at end of file + +def import_sound(filepath): + scn = bpy.context.scene + + strip = scn.sequence_editor.sequences.new_sound( + name=filepath.stem, + filepath=str(filepath), + channel=get_channel('Audio'), + frame_start=scn.frame_start + ) + + if bpy.data.is_saved: + strip.sound.filepath = bpy.path.relpath(str(filepath)) + + strip.show_waveform = True + return strip + + +def clean_sequencer(edit=False, movie=False, sound=False): + scn = bpy.context.scene + sequences = [] + + if edit: + sequences.extend(get_strips('Shots')) + if movie: + sequences.extend(get_strips('Movie')) + if sound: + sequences.extend(get_strips('Audio')) + + for sequence in sequences: + scn.sequence_editor.sequences.remove(sequence) \ No newline at end of file