waveform
parent
d0230672f4
commit
5c35bae0e3
|
@ -83,28 +83,12 @@ class VSETB_OT_tracker_connect(Operator):
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
|
||||||
def get_custom_name_items(self, context):
|
|
||||||
settings = get_scene_settings()
|
|
||||||
project = settings.active_project
|
|
||||||
return [(m.field_name, m.name, '') for m in project.metadata_types if m.entity_type=='ASSET']
|
|
||||||
|
|
||||||
class VSETB_OT_export_spreadsheet(Operator):
|
class VSETB_OT_export_spreadsheet(Operator):
|
||||||
bl_idname = "vse_toolbox.export_spreadsheet"
|
bl_idname = "vse_toolbox.export_spreadsheet"
|
||||||
bl_label = "Export Spreadsheet"
|
bl_label = "Export Spreadsheet"
|
||||||
bl_description = "Export Shot data in a table as a csv or an xlsl"
|
bl_description = "Export Shot data in a table as a csv or an xlsl"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
format : EnumProperty(items=[(i, i, '') for i in ('CSV', 'XLSX')])
|
|
||||||
separator : StringProperty(default='\\n')
|
|
||||||
delimiter : StringProperty(default=';')
|
|
||||||
export_path : StringProperty(default='//export')
|
|
||||||
use_custom_name : BoolProperty(default=False)
|
|
||||||
custom_name : EnumProperty(items=get_custom_name_items,
|
|
||||||
description='Use a custom name for asset using a metadata value')
|
|
||||||
|
|
||||||
open_folder : BoolProperty(default=False)
|
|
||||||
show_settings : BoolProperty(default=False)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
settings = get_scene_settings()
|
settings = get_scene_settings()
|
||||||
|
@ -120,6 +104,7 @@ class VSETB_OT_export_spreadsheet(Operator):
|
||||||
scn = context.scene
|
scn = context.scene
|
||||||
settings = get_scene_settings()
|
settings = get_scene_settings()
|
||||||
project = settings.active_project
|
project = settings.active_project
|
||||||
|
options = project.spreadsheet_options
|
||||||
|
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|
||||||
|
@ -135,30 +120,31 @@ class VSETB_OT_export_spreadsheet(Operator):
|
||||||
|
|
||||||
row = col.row(align=True, heading='Custom Name')
|
row = col.row(align=True, heading='Custom Name')
|
||||||
#row.use_property_split = True
|
#row.use_property_split = True
|
||||||
row.prop(self, 'use_custom_name', text='')
|
row.prop(options, 'use_custom_name', text='')
|
||||||
sub = row.row(align=True)
|
sub = row.row(align=True)
|
||||||
sub.enabled = self.use_custom_name
|
sub.enabled = options.use_custom_name
|
||||||
sub.prop(self, 'custom_name', text='')
|
sub.prop(options, 'custom_name', text='')
|
||||||
|
|
||||||
col.separator()
|
col.separator()
|
||||||
|
|
||||||
row = col.row(align=False)
|
row = col.row(align=False)
|
||||||
row.prop(self, "format", expand=True, text='Format')
|
row.prop(options, "format", expand=True, text='Format')
|
||||||
row.prop(self, 'show_settings', text='', icon='PREFERENCES')
|
row.prop(options, 'show_settings', text='', icon='PREFERENCES')
|
||||||
if self.show_settings:
|
if options.show_settings:
|
||||||
col.prop(self, "separator", expand=True, text='Separator')
|
col.prop(options, "separator", expand=True, text='Separator')
|
||||||
if self.format == 'CSV':
|
if options.format == 'CSV':
|
||||||
col.prop(self, "delimiter", expand=True, text='Delimiter')
|
col.prop(options, "delimiter", expand=True, text='Delimiter')
|
||||||
|
|
||||||
col.separator()
|
col.separator()
|
||||||
col.prop(self, 'open_folder', text='Open Folder')
|
col.prop(options, 'open_folder', text='Open Folder')
|
||||||
col.prop(self, 'export_path', text='Export Path')
|
col.prop(options, 'export_path', text='Export Path')
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
#self.report({'ERROR'}, f'Export not implemented yet.')
|
#self.report({'ERROR'}, f'Export not implemented yet.')
|
||||||
prefs = get_addon_prefs()
|
prefs = get_addon_prefs()
|
||||||
settings = get_scene_settings()
|
settings = get_scene_settings()
|
||||||
project = settings.active_project
|
project = settings.active_project
|
||||||
|
options = project.spreadsheet_options
|
||||||
episode = settings.active_episode
|
episode = settings.active_episode
|
||||||
|
|
||||||
cells = [cell for cell in project.spreadsheet if cell.enabled]
|
cells = [cell for cell in project.spreadsheet if cell.enabled]
|
||||||
|
@ -167,8 +153,8 @@ class VSETB_OT_export_spreadsheet(Operator):
|
||||||
# Header
|
# Header
|
||||||
rows.append([cell.export_name for cell in cells])
|
rows.append([cell.export_name for cell in cells])
|
||||||
|
|
||||||
separator = self.separator.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r')
|
separator = options.separator.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r')
|
||||||
delimiter = self.delimiter.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r')
|
delimiter = options.delimiter.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r')
|
||||||
|
|
||||||
for strip in get_strips('Shots'):
|
for strip in get_strips('Shots'):
|
||||||
row = []
|
row = []
|
||||||
|
@ -182,11 +168,11 @@ class VSETB_OT_export_spreadsheet(Operator):
|
||||||
if not asset.asset_type == cell.name:
|
if not asset.asset_type == cell.name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self.use_custom_name:
|
if options.use_custom_name:
|
||||||
if asset.get('metadata', {}).get(self.custom_name):
|
if asset.get('metadata', {}).get(options.custom_name):
|
||||||
asset_castings.append(asset['metadata'][self.custom_name])
|
asset_castings.append(asset['metadata'][options.custom_name])
|
||||||
else:
|
else:
|
||||||
self.report({'ERROR'}, f'The asset {asset.tracker_name} has no data {self.custom_name}')
|
self.report({'ERROR'}, f'The asset {asset.tracker_name} has no data {options.custom_name}')
|
||||||
else:
|
else:
|
||||||
asset_castings.append(asset.tracker_name)
|
asset_castings.append(asset.tracker_name)
|
||||||
|
|
||||||
|
@ -206,7 +192,7 @@ class VSETB_OT_export_spreadsheet(Operator):
|
||||||
|
|
||||||
#print(rows)
|
#print(rows)
|
||||||
|
|
||||||
export_path = Path(os.path.abspath(bpy.path.abspath(self.export_path)))
|
export_path = Path(os.path.abspath(bpy.path.abspath(options.export_path)))
|
||||||
export_name = export_path.name
|
export_name = export_path.name
|
||||||
|
|
||||||
if export_path.suffix or export_name.endswith('{ext}'):
|
if export_path.suffix or export_name.endswith('{ext}'):
|
||||||
|
@ -221,7 +207,7 @@ class VSETB_OT_export_spreadsheet(Operator):
|
||||||
date = datetime.now().strftime('%Y_%m_%d')
|
date = datetime.now().strftime('%Y_%m_%d')
|
||||||
project_name = project.name.replace(' ', '_').lower()
|
project_name = project.name.replace(' ', '_').lower()
|
||||||
episode_name = episode.name.replace(' ', '_').lower() if episode else 'episode'
|
episode_name = episode.name.replace(' ', '_').lower() if episode else 'episode'
|
||||||
ext = self.format.lower()
|
ext = options.format.lower()
|
||||||
|
|
||||||
export_name = export_name.format(date=date, project=project_name,
|
export_name = export_name.format(date=date, project=project_name,
|
||||||
episode=episode_name, tracker=settings.tracker_name.lower(), ext=ext)
|
episode=episode_name, tracker=settings.tracker_name.lower(), ext=ext)
|
||||||
|
@ -231,14 +217,14 @@ class VSETB_OT_export_spreadsheet(Operator):
|
||||||
#2023_04_11_kitsu_boris_ep01_shots
|
#2023_04_11_kitsu_boris_ep01_shots
|
||||||
export_path.parent.mkdir(parents=True, exist_ok=True)
|
export_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
if self.format == 'CSV':
|
if options.format == 'CSV':
|
||||||
print('Writing .csv file to', export_path)
|
print('Writing .csv file to', export_path)
|
||||||
with open(str(export_path), 'w', newline='\n', encoding='utf-8') as f:
|
with open(str(export_path), 'w', newline='\n', encoding='utf-8') as f:
|
||||||
writer = csv.writer(f, delimiter=self.delimiter)
|
writer = csv.writer(f, delimiter=options.delimiter)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
writer.writerow(row)
|
writer.writerow(row)
|
||||||
|
|
||||||
elif self.format == 'XLSX':
|
elif options.format == 'XLSX':
|
||||||
try:
|
try:
|
||||||
import openpyxl
|
import openpyxl
|
||||||
except ModuleNotFoundError():
|
except ModuleNotFoundError():
|
||||||
|
@ -259,7 +245,7 @@ class VSETB_OT_export_spreadsheet(Operator):
|
||||||
# Save the file
|
# Save the file
|
||||||
workbook.save(str(export_path))
|
workbook.save(str(export_path))
|
||||||
|
|
||||||
if self.open_folder:
|
if options.open_folder:
|
||||||
open_file(export_path, select=True)
|
open_file(export_path, select=True)
|
||||||
|
|
||||||
|
|
||||||
|
@ -745,8 +731,8 @@ class VSETB_OT_rename(Operator):
|
||||||
bl_description = "Rename Strips"
|
bl_description = "Rename Strips"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
template : StringProperty(name="Strip Template Name", default="")
|
template : StringProperty(name="Strip Name", default="")
|
||||||
increment : IntProperty(name="Name Increment", default=0)
|
increment : IntProperty(name="Increment", default=0)
|
||||||
channel_name : StringProperty(name="Channel Name", default="")
|
channel_name : StringProperty(name="Channel Name", default="")
|
||||||
#selected_only : BoolProperty(name="Selected Only", default=False)
|
#selected_only : BoolProperty(name="Selected Only", default=False)
|
||||||
start_number : IntProperty(name="Start Number", default=0, min=0)
|
start_number : IntProperty(name="Start Number", default=0, min=0)
|
||||||
|
@ -838,6 +824,27 @@ class VSETB_OT_render(Operator):
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class VSETB_OT_show_waveform(Operator):
|
||||||
|
bl_idname = "vse_toolbox.show_waveform"
|
||||||
|
bl_label = "Show Waveform"
|
||||||
|
bl_description = "Show Waveform of all audio strips"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
enabled : BoolProperty(default=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
scn = context.scene
|
||||||
|
|
||||||
|
for strip in get_strips(channel='Audio'):
|
||||||
|
strip.show_waveform = self.enabled
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class VSETB_OT_set_sequencer(Operator):
|
class VSETB_OT_set_sequencer(Operator):
|
||||||
bl_idname = "vse_toolbox.set_sequencer"
|
bl_idname = "vse_toolbox.set_sequencer"
|
||||||
bl_label = "Set Sequencer"
|
bl_label = "Set Sequencer"
|
||||||
|
@ -1193,7 +1200,8 @@ classes = (
|
||||||
VSETB_OT_set_sequencer,
|
VSETB_OT_set_sequencer,
|
||||||
VSETB_OT_tracker_connect,
|
VSETB_OT_tracker_connect,
|
||||||
VSETB_OT_set_stamps,
|
VSETB_OT_set_stamps,
|
||||||
VSETB_OT_upload_to_tracker
|
VSETB_OT_upload_to_tracker,
|
||||||
|
VSETB_OT_show_waveform
|
||||||
)
|
)
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
|
|
|
@ -7,7 +7,7 @@ from bpy.types import Panel
|
||||||
|
|
||||||
from vse_toolbox.bl_utils import (get_addon_prefs, get_scene_settings, get_strip_settings)
|
from vse_toolbox.bl_utils import (get_addon_prefs, get_scene_settings, get_strip_settings)
|
||||||
from vse_toolbox.constants import ASSET_PREVIEWS
|
from vse_toolbox.constants import ASSET_PREVIEWS
|
||||||
from vse_toolbox.sequencer_utils import (set_active_strip, get_channel_name)
|
from vse_toolbox.sequencer_utils import (set_active_strip, get_channel_name, get_strips)
|
||||||
|
|
||||||
|
|
||||||
class VSETB_main:
|
class VSETB_main:
|
||||||
|
@ -74,6 +74,12 @@ class VSETB_PT_sequencer(VSETB_main, Panel):
|
||||||
|
|
||||||
def draw_header_preset(self, context):
|
def draw_header_preset(self, context):
|
||||||
settings = get_scene_settings()
|
settings = get_scene_settings()
|
||||||
|
|
||||||
|
audio_strips = get_strips('Audio')
|
||||||
|
|
||||||
|
depress = any(s.show_waveform for s in audio_strips)
|
||||||
|
self.layout.operator('vse_toolbox.show_waveform', text="", icon="IPO_ELASTIC", depress=depress).enabled = not depress
|
||||||
|
|
||||||
ico = ("RESTRICT_SELECT_OFF" if settings.auto_select_strip else "RESTRICT_SELECT_ON")
|
ico = ("RESTRICT_SELECT_OFF" if settings.auto_select_strip else "RESTRICT_SELECT_ON")
|
||||||
self.layout.prop(settings, "auto_select_strip", text="", icon=ico)
|
self.layout.prop(settings, "auto_select_strip", text="", icon=ico)
|
||||||
|
|
||||||
|
|
|
@ -155,6 +155,23 @@ class Episode(PropertyGroup):
|
||||||
settings = get_scene_settings()
|
settings = get_scene_settings()
|
||||||
return self.get(settings.project_name)
|
return self.get(settings.project_name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_custom_name_items(self, context):
|
||||||
|
settings = get_scene_settings()
|
||||||
|
project = settings.active_project
|
||||||
|
return [(m.field_name, m.name, '') for m in project.metadata_types if m.entity_type=='ASSET']
|
||||||
|
|
||||||
|
class SpreadsheetOptions(PropertyGroup):
|
||||||
|
format : EnumProperty(items=[(i, i, '') for i in ('CSV', 'XLSX')])
|
||||||
|
separator : StringProperty(default='\\n')
|
||||||
|
delimiter : StringProperty(default=';')
|
||||||
|
export_path : StringProperty(default='//export')
|
||||||
|
use_custom_name : BoolProperty(default=False)
|
||||||
|
custom_name : EnumProperty(items=get_custom_name_items,
|
||||||
|
description='Use a custom name for asset using a metadata value')
|
||||||
|
|
||||||
|
open_folder : BoolProperty(default=False)
|
||||||
|
show_settings : BoolProperty(default=False)
|
||||||
|
|
||||||
class Project(PropertyGroup):
|
class Project(PropertyGroup):
|
||||||
id : StringProperty(default='')
|
id : StringProperty(default='')
|
||||||
|
@ -188,6 +205,7 @@ class Project(PropertyGroup):
|
||||||
task_types : CollectionProperty(type=TaskType)
|
task_types : CollectionProperty(type=TaskType)
|
||||||
task_statuses : CollectionProperty(type=TaskStatus)
|
task_statuses : CollectionProperty(type=TaskStatus)
|
||||||
|
|
||||||
|
spreadsheet_options : PointerProperty(type=SpreadsheetOptions)
|
||||||
spreadsheet : CollectionProperty(type=SpreadsheetCell)
|
spreadsheet : CollectionProperty(type=SpreadsheetCell)
|
||||||
spreadsheet_index : IntProperty(name='Spreadsheet Index', default=0)
|
spreadsheet_index : IntProperty(name='Spreadsheet Index', default=0)
|
||||||
|
|
||||||
|
@ -396,6 +414,7 @@ classes=(
|
||||||
Metadata,
|
Metadata,
|
||||||
MetadataType,
|
MetadataType,
|
||||||
TaskType,
|
TaskType,
|
||||||
|
SpreadsheetOptions,
|
||||||
Project,
|
Project,
|
||||||
VSETB_UL_spreadsheet,
|
VSETB_UL_spreadsheet,
|
||||||
VSETB_UL_casting,
|
VSETB_UL_casting,
|
||||||
|
|
|
@ -9,7 +9,9 @@ from bpy.app.handlers import persistent
|
||||||
|
|
||||||
from vse_toolbox.bl_utils import get_scene_settings, get_strip_settings
|
from vse_toolbox.bl_utils import get_scene_settings, get_strip_settings
|
||||||
from vse_toolbox.constants import SOUND_SUFFIXES
|
from vse_toolbox.constants import SOUND_SUFFIXES
|
||||||
import multiprocessing
|
#import multiprocessing
|
||||||
|
#from multiprocessing.pool import ThreadPool
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
def new_text_strip(name='Text', channel=0, start=0, end=50, text='Text', font_size=48,
|
def new_text_strip(name='Text', channel=0, start=0, end=50, text='Text', font_size=48,
|
||||||
|
@ -131,18 +133,69 @@ def get_strip_render_path(strip, template):
|
||||||
render_path = template.format(strip_name=strip.name, ext=suffix[1:])
|
render_path = template.format(strip_name=strip.name, ext=suffix[1:])
|
||||||
return Path(os.path.abspath(bpy.path.abspath(render_path)))
|
return Path(os.path.abspath(bpy.path.abspath(render_path)))
|
||||||
|
|
||||||
def render_strips(strips, template):
|
'''
|
||||||
scn = bpy.context.scene
|
# def render_strip_background(blender_path, filepath, start, end, output):
|
||||||
scene_start = scn.frame_start
|
# cmd = [
|
||||||
scene_end = scn.frame_end
|
# blender_path, '-b', '--factory-startup', str(tmp_path), '-a',
|
||||||
render_path = scn.render.filepath
|
# '-s', str(start), '-e', str(end),
|
||||||
|
# '-o', str(output)
|
||||||
|
# ]
|
||||||
|
|
||||||
# pool = multiprocessing.Pool(4)
|
# print(cmd)
|
||||||
# p.map(func, range(1, 100))
|
# process = subprocess.call(cmd)
|
||||||
|
|
||||||
|
def render_strips(strips, template):
|
||||||
|
from functools import partial
|
||||||
|
scn = bpy.context.scene
|
||||||
|
# scene_start = scn.frame_start
|
||||||
|
# scene_end = scn.frame_end
|
||||||
|
# render_path = scn.render.filepath
|
||||||
|
|
||||||
|
tmp_name = Path(bpy.data.filepath).name if bpy.data.filepath else 'Untitled.blend'
|
||||||
|
tmp_path = Path(bpy.app.tempdir, tmp_name)
|
||||||
|
|
||||||
|
bpy.ops.wm.save_as_mainfile(filepath=str(tmp_path), copy=True)
|
||||||
|
|
||||||
|
|
||||||
|
script_code = dedent(f"""
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
for
|
||||||
|
|
||||||
|
""")
|
||||||
|
|
||||||
|
script_path = Path(bpy.app.tempdir) / 'bundle_library.py'
|
||||||
|
script_path.write_text(script_code)
|
||||||
|
|
||||||
|
cmd = [bpy.app.binary_path, tmp_path, '--python', ]
|
||||||
|
|
||||||
|
# nb_threads = min(multiprocessing.cpu_count()-2, 8)
|
||||||
|
# print(nb_threads)
|
||||||
|
|
||||||
|
# pool = multiprocessing.Pool(nb_threads)
|
||||||
|
|
||||||
|
#arguments = [(bpy.app.binary_path, str(tmp_path), s.frame_final_start, s.frame_final_end-1, str(get_strip_render_path(s, template))) for s in strips]
|
||||||
|
#print(arguments)
|
||||||
|
#pool.starmap(render_strip_background, arguments)
|
||||||
|
|
||||||
# def render_strip_background(index):
|
# def render_strip_background(index):
|
||||||
# cmd = [bpy.app.binary_path, etc]
|
# cmd = [bpy.app.binary_path, etc]
|
||||||
# process = subprocess.Popen(substr + " --index {}".format(index), shell=True, stdout=subprocess.PIPE)
|
# process = subprocess.Popen(cmd)
|
||||||
|
|
||||||
|
# pool = ThreadPool(nb_threads)
|
||||||
|
|
||||||
|
# for strip in strips:
|
||||||
|
# start = strip.frame_final_start
|
||||||
|
# end = strip.frame_final_end-1
|
||||||
|
# output = str(get_strip_render_path(strip, template))
|
||||||
|
|
||||||
|
# cmd = [
|
||||||
|
# bpy.app.binary_path, '-b', str(tmp_path),
|
||||||
|
# '-s', str(start), '-e', str(end), '-a',
|
||||||
|
# '-o', str(output)
|
||||||
|
# ]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for strip in strips:
|
for strip in strips:
|
||||||
|
@ -161,6 +214,25 @@ def render_strips(strips, template):
|
||||||
bpy.ops.render.opengl(animation=True, sequencer=True)
|
bpy.ops.render.opengl(animation=True, sequencer=True)
|
||||||
|
|
||||||
|
|
||||||
|
scn.frame_start = scene_start
|
||||||
|
scn.frame_end = scene_end
|
||||||
|
scn.render.filepath = render_path
|
||||||
|
'''
|
||||||
|
|
||||||
|
def render_strips(strips, template):
|
||||||
|
scn = bpy.context.scene
|
||||||
|
scene_start = scn.frame_start
|
||||||
|
scene_end = scn.frame_end
|
||||||
|
render_path = scn.render.filepath
|
||||||
|
|
||||||
|
for strip in strips:
|
||||||
|
scn.frame_start = strip.frame_final_start
|
||||||
|
scn.frame_end = strip.frame_final_end - 1
|
||||||
|
scn.render.filepath = str(get_strip_render_path(strip, template))
|
||||||
|
|
||||||
|
print(f'Render Strip to {scn.render.filepath}')
|
||||||
|
bpy.ops.render.opengl(animation=True, sequencer=True)
|
||||||
|
|
||||||
scn.frame_start = scene_start
|
scn.frame_start = scene_start
|
||||||
scn.frame_end = scene_end
|
scn.frame_end = scene_end
|
||||||
scn.render.filepath = render_path
|
scn.render.filepath = render_path
|
||||||
|
@ -278,7 +350,8 @@ def import_sound(filepath):
|
||||||
if bpy.data.is_saved:
|
if bpy.data.is_saved:
|
||||||
strip.sound.filepath = bpy.path.relpath(str(filepath))
|
strip.sound.filepath = bpy.path.relpath(str(filepath))
|
||||||
|
|
||||||
strip.show_waveform = True
|
strip.show_waveform = True if strip.frame_final_duration < 10000 else False
|
||||||
|
|
||||||
return strip
|
return strip
|
||||||
|
|
||||||
def clean_sequencer(edit=False, movie=False, sound=False):
|
def clean_sequencer(edit=False, movie=False, sound=False):
|
||||||
|
|
Loading…
Reference in New Issue