import edl, import movie, import audio

pull/5/head
Clément Ducarteron 2023-03-16 18:32:17 +01:00
parent 2973fd85d8
commit c75c5469f9
5 changed files with 298 additions and 52 deletions

View File

@ -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", "")

View File

@ -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):
@ -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)
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,

View File

@ -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')

View File

@ -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,
)

View File

@ -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<s.frame_final_end), '')
@ -59,7 +69,7 @@ def rename_strips(
def set_channels():
scn = bpy.context.scene
settings = get_settings()
items = settings.rna_type.bl_rna.properties['channels'].enum_items
items = settings.rna_type.bl_rna.properties['channel'].enum_items
for i, c in enumerate(items.keys(), start=1):
scn.sequence_editor.channels[i].name = c.title()
@ -68,18 +78,35 @@ def render_strips(strips):
for strip in strips:
print(strip.name)
def import_edl(filepath, adapter="cmx_3600"):
def import_edit(filepath, adapter="cmx_3600", clean_sequencer=False):
import opentimelineio as otio
scn = bpy.context.scene
from opentimelineio.schema import (
Clip,
ExternalReference,
Gap,
ImageSequenceReference,
Stack,
Timeline,
Track,
)
scn = bpy.context.scene
sequences = scn.sequence_editor.sequences
edl = Path(filepath)
if clean_sequencer:
movie = get_strips(channel='Movie')
audio = get_strips(channel='Audio')
for strip in sequences:
if strip not in (movie+audio):
sequences.remove(strip)
edl = Path(filepath)
try:
timeline = otio.adapters.read_from_file(
str(edl), adapter, rate=scn.render.fps, ignore_timecode_mismatch=True)
except:
print("[>.] 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,
frame_start=frame_start,
frame_end=frame_end,
)
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)
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,
)
strip.blend_alpha = 0.0
except Exception as e:
print('e: ', e)
continue
scn.frame_end = frame_end-1
return timeline
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
"""
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)