vse_toolbox/operators/operators.py

1205 lines
39 KiB
Python
Raw Normal View History

2023-03-14 13:38:04 +01:00
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
import importlib
2023-03-21 18:33:29 +01:00
import json
2023-03-14 13:38:04 +01:00
import re
2023-04-20 00:12:39 +02:00
import time
2023-04-21 21:44:05 +02:00
from pprint import pprint
import csv
from datetime import datetime
import os
2023-03-14 13:38:04 +01:00
import vse_toolbox
from bpy_extras.io_utils import ImportHelper
from bpy.props import (
CollectionProperty,
BoolProperty,
EnumProperty,
IntProperty,
StringProperty,
)
from bpy.types import (
Operator,
)
from pathlib import Path
2023-03-16 18:32:17 +01:00
from vse_toolbox.constants import (
2023-03-20 19:05:22 +01:00
ASSET_PREVIEWS,
2023-03-21 18:33:29 +01:00
CASTING_BUFFER,
2023-03-20 19:05:22 +01:00
CONFIG_DIR,
2023-03-16 18:32:17 +01:00
EDITS,
EDIT_SUFFIXES,
2023-03-20 19:05:22 +01:00
MOVIES,
2023-03-16 18:32:17 +01:00
MOVIE_SUFFIXES,
2023-03-20 19:05:22 +01:00
PREVIEWS_DIR,
SOUNDS,
2023-03-16 18:32:17 +01:00
SOUND_SUFFIXES,
2023-04-20 00:12:39 +02:00
TASK_ITEMS
2023-03-16 18:32:17 +01:00
)
2023-03-14 13:38:04 +01:00
from vse_toolbox.sequencer_utils import (
2023-03-16 18:32:17 +01:00
clean_sequencer,
2023-03-14 13:38:04 +01:00
get_strips,
2023-04-14 18:55:00 +02:00
set_active_strip,
2023-03-16 18:32:17 +01:00
import_edit,
import_movie,
import_sound,
2023-03-14 13:38:04 +01:00
rename_strips,
render_strips,
set_channels,
2023-04-14 18:55:00 +02:00
get_channel_index,
2023-04-20 00:12:39 +02:00
new_text_strip,
get_strip_render_path,
get_strip_sequence_name
2023-03-14 13:38:04 +01:00
)
2023-04-20 00:12:39 +02:00
2023-03-21 18:33:29 +01:00
from vse_toolbox.bl_utils import get_addon_prefs, get_scene_settings, get_strip_settings
2023-04-22 15:42:38 +02:00
from vse_toolbox.file_utils import install_module, norm_name, norm_str, open_file
2023-03-14 13:38:04 +01:00
2023-04-14 18:55:00 +02:00
2023-03-24 11:42:17 +01:00
class VSETB_OT_tracker_connect(Operator):
bl_idname = "vse_toolbox.tracker_connect"
bl_label = "Connect to Tracker"
bl_description = "Connect to Tracker"
@classmethod
def poll(cls, context):
prefs = get_addon_prefs()
if prefs.tracker:
return True
def execute(self, context):
prefs = get_addon_prefs()
settings = get_scene_settings()
try:
prefs.tracker.connect()
self.report({'INFO'}, f'Sucessfully login to {settings.tracker_name.title()}')
return {"FINISHED"}
except Exception as e:
print('e: ', e)
self.report({'ERROR'}, f'Cannot connect to tracker.')
return {"CANCELLED"}
2023-04-22 15:42:38 +02:00
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']
2023-04-21 21:44:05 +02:00
class VSETB_OT_export_spreadsheet(Operator):
bl_idname = "vse_toolbox.export_spreadsheet"
bl_label = "Export Spreadsheet"
bl_description = "Export Shot data in a table as a csv or an xlsl"
2023-03-14 13:38:04 +01:00
bl_options = {"REGISTER", "UNDO"}
2023-04-22 15:42:38 +02:00
format : EnumProperty(items=[(i, i, '') for i in ('CSV', 'XLSX')])
2023-04-21 21:44:05 +02:00
separator : StringProperty(default='\\n')
delimiter : StringProperty(default=';')
export_path : StringProperty(default='//export')
2023-04-22 15:42:38 +02:00
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)
2023-04-21 21:44:05 +02:00
2023-03-14 13:38:04 +01:00
@classmethod
def poll(cls, context):
2023-04-21 21:44:05 +02:00
settings = get_scene_settings()
return settings.active_project
def invoke(self, context, event):
settings = get_scene_settings()
project = settings.active_project
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
scn = context.scene
settings = get_scene_settings()
project = settings.active_project
layout = self.layout
row = layout.row()
row.template_list("VSETB_UL_spreadsheet", "spreadsheet", project, "spreadsheet", project, "spreadsheet_index", rows=8)
col_tool = row.column(align=True)
col_tool.operator('vse_toolbox.spreadsheet_move', icon='TRIA_UP', text="").direction = 'UP'
col_tool.operator('vse_toolbox.spreadsheet_move', icon='TRIA_DOWN', text="").direction = 'DOWN'
col = layout.column()
col.use_property_split = True
2023-04-22 15:42:38 +02:00
row = col.row(align=True, heading='Custom Name')
#row.use_property_split = True
row.prop(self, 'use_custom_name', text='')
sub = row.row(align=True)
sub.enabled = self.use_custom_name
sub.prop(self, 'custom_name', text='')
col.separator()
row = col.row(align=False)
2023-04-21 21:44:05 +02:00
row.prop(self, "format", expand=True, text='Format')
2023-04-22 15:42:38 +02:00
row.prop(self, 'show_settings', text='', icon='PREFERENCES')
if self.show_settings:
col.prop(self, "separator", expand=True, text='Separator')
if self.format == 'CSV':
col.prop(self, "delimiter", expand=True, text='Delimiter')
2023-04-21 21:44:05 +02:00
2023-04-22 15:42:38 +02:00
col.separator()
col.prop(self, 'open_folder', text='Open Folder')
col.prop(self, 'export_path', text='Export Path')
2023-03-14 13:38:04 +01:00
def execute(self, context):
2023-04-21 21:44:05 +02:00
#self.report({'ERROR'}, f'Export not implemented yet.')
prefs = get_addon_prefs()
settings = get_scene_settings()
project = settings.active_project
episode = settings.active_episode
cells = [cell for cell in project.spreadsheet if cell.enabled]
rows = []
# Header
rows.append([cell.export_name for cell in cells])
2023-04-22 15:42:38 +02:00
separator = self.separator.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r')
delimiter = self.delimiter.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r')
2023-04-21 21:44:05 +02:00
for strip in get_strips('Shots'):
row = []
for cell in cells:
2023-04-22 15:42:38 +02:00
if cell.type == "METADATA":
2023-04-21 21:44:05 +02:00
row += [getattr(strip.vsetb_strip_settings.metadata, cell.field_name)]
2023-04-22 15:42:38 +02:00
elif cell.type == "ASSET_TYPE":
asset_castings = []
for asset_casting in strip.vsetb_strip_settings.casting:
asset = asset_casting.asset
if not asset.asset_type == cell.name:
continue
if self.use_custom_name:
if asset.get('metadata', {}).get(self.custom_name):
asset_castings.append(asset['metadata'][self.custom_name])
else:
self.report({'ERROR'}, f'The asset {asset.tracker_name} has no data {self.custom_name}')
else:
asset_castings.append(asset.tracker_name)
row += [separator.join(asset_castings)]
2023-04-21 21:44:05 +02:00
elif cell.field_name == 'EPISODE':
row += [settings.active_episode.name]
elif cell.field_name == 'SEQUENCE':
row += [get_strip_sequence_name(strip)]
elif cell.field_name == 'SHOT':
row += [strip.name]
elif cell.field_name == 'DESCRIPTION':
row += [strip.vsetb_strip_settings.description]
elif cell.field_name == 'FRAMES':
row += [strip.frame_final_duration]
rows.append(row)
#print(rows)
export_path = Path(os.path.abspath(bpy.path.abspath(self.export_path)))
export_name = export_path.name
if export_path.suffix or export_name.endswith('{ext}'):
export_path = export_path.parent
else: # It's a directory
if project.type == 'TVSHOW':
export_name = '{date}_{project}_{episode}_{tracker}_shots.{ext}'
else:
export_name = '{date}_{project}_{tracker}_shots.{ext}'
date = datetime.now().strftime('%Y_%m_%d')
project_name = project.name.replace(' ', '_').lower()
episode_name = episode.name.replace(' ', '_').lower() if episode else 'episode'
ext = self.format.lower()
export_name = export_name.format(date=date, project=project_name,
episode=episode_name, tracker=settings.tracker_name.lower(), ext=ext)
export_path = export_path / export_name
#2023_04_11_kitsu_boris_ep01_shots
export_path.parent.mkdir(parents=True, exist_ok=True)
2023-03-14 13:38:04 +01:00
2023-04-22 15:42:38 +02:00
if self.format == 'CSV':
print('Writing .csv file to', export_path)
with open(str(export_path), 'w', newline='\n', encoding='utf-8') as f:
writer = csv.writer(f, delimiter=self.delimiter)
for row in rows:
writer.writerow(row)
elif self.format == 'XLSX':
try:
import openpyxl
except ModuleNotFoundError():
self.report({'INFO'}, 'Installing openpyxl')
openpyxl = install_module('openpyxl')
from openpyxl import Workbook
workbook = Workbook()
worksheet = workbook.active
2023-04-21 21:44:05 +02:00
for row in rows:
2023-04-22 15:42:38 +02:00
worksheet.append(row)
2023-04-21 21:44:05 +02:00
2023-04-22 15:42:38 +02:00
for col in worksheet.columns:
letter = col[0].column_letter
worksheet.column_dimensions[letter].auto_size = True
# Save the file
workbook.save(str(export_path))
if self.open_folder:
open_file(export_path, select=True)
2023-04-21 21:44:05 +02:00
return {"FINISHED"}
2023-03-14 13:38:04 +01:00
2023-04-20 00:12:39 +02:00
def get_task_status_items(self, context):
settings = get_scene_settings()
project = settings.active_project
status_items = [('CURRENT', 'Current', '')]
2023-04-20 18:16:34 +02:00
if project:
status_items += [(t.name, t.name, '') for t in project.task_statuses]
2023-04-20 00:12:39 +02:00
return status_items
def get_task_type_items(self, context):
settings = get_scene_settings()
project = settings.active_project
2023-04-20 18:16:34 +02:00
if not project:
return [('NONE', 'None', '')]
2023-04-20 00:12:39 +02:00
return [(t.name, t.name, '') for t in project.task_types]
class VSETB_OT_upload_to_tracker(Operator):
bl_idname = "vse_toolbox.upload_to_tracker"
bl_label = "Upload to tracker"
bl_description = "Upload selected strip to tracker"
bl_options = {"REGISTER", "UNDO"}
2023-04-21 21:44:05 +02:00
task : EnumProperty(items=get_task_type_items)
status : EnumProperty(items=get_task_status_items)
2023-04-20 00:12:39 +02:00
comment : StringProperty()
2023-04-21 21:44:05 +02:00
add_preview : BoolProperty(default=True)
2023-04-22 15:42:38 +02:00
preview_mode : EnumProperty(items=[(m, m.title().replace('_', ' '), '')
for m in ('ONLY_NEW', 'REPLACE', 'ADD')])
2023-04-21 21:44:05 +02:00
set_main_preview : BoolProperty(default=True)
2023-04-20 00:12:39 +02:00
casting : BoolProperty(default=True)
custom_data : BoolProperty(default=True)
@classmethod
def poll(cls, context):
return True
def invoke(self, context, event):
prefs = get_addon_prefs()
settings = get_scene_settings()
project = settings.active_project
tracker = prefs.tracker
tracker.connect()
#self.bl_label = f"Upload to {settings.tracker_name.title()}"
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
scn = context.scene
settings = get_scene_settings()
layout = self.layout
col = layout.column()
col.use_property_split = True
col.prop(self, 'task', text='Task')
col.prop(self, 'status', text='Status')
col.prop(self, 'comment', text='Comment')
row = col.row(heading='Add Preview')
row.prop(self, 'add_preview', text='')
row.prop(self, 'preview_mode', text='')
col.separator()
col.prop(self, 'casting', text='Casting')
col.prop(self, 'custom_data', text='Custom Data')
def execute(self, context):
#self.report({'ERROR'}, f'Export not implemented yet.')
prefs = get_addon_prefs()
settings = get_scene_settings()
2023-04-20 18:16:34 +02:00
project = settings.active_project
2023-04-20 00:12:39 +02:00
tracker = prefs.tracker
status = self.status
if status == 'CURRENT':
status = None
for strip in get_strips(channel='Shots', selected_only=True):
sequence_name = get_strip_sequence_name(strip)
shot_name = strip.name
sequence = tracker.get_sequence(sequence_name)
2023-04-20 18:16:34 +02:00
metadata = strip.vsetb_strip_settings.metadata.to_dict()
2023-04-21 21:44:05 +02:00
#print(metadata)
2023-04-20 18:16:34 +02:00
2023-04-20 00:12:39 +02:00
if not sequence:
self.report({"INFO"}, f'Create sequence {sequence_name} in Kitsu')
sequence = tracker.new_sequence(sequence_name)
shot = tracker.get_shot(shot_name, sequence=sequence)
if not shot:
self.report({"INFO"}, f'Create shot {shot_name} in Kitsu')
shot = tracker.new_shot(shot_name, sequence=sequence)
task = tracker.get_task(self.task, entity=shot)
2023-04-20 18:16:34 +02:00
if not task:
task = tracker.new_task(shot, task_type=self.task)
# print('\n', 'task comment')
# print(task['last_comment'])
preview = None
if self.add_preview:
preview = Path(get_strip_render_path(strip, project.render_template))
2023-04-21 21:44:05 +02:00
#print(preview)
2023-04-20 18:16:34 +02:00
if not preview.exists():
preview = None
2023-04-21 21:44:05 +02:00
elif task.get('last_comment') and task['last_comment']['previews']:
2023-04-20 18:16:34 +02:00
if self.preview_mode == 'REPLACE':
tracker.remove_comment(task['last_comment'])
elif self.preview_mode == 'ONLY_NEW':
preview = None
2023-04-20 00:12:39 +02:00
2023-04-21 21:44:05 +02:00
#print(f'{preview=}')
#print(f'{status=}')
if status or preview:
tracker.new_comment(task, comment=self.comment, status=status, preview=preview, set_main_preview=self.set_main_preview)
2023-04-20 18:16:34 +02:00
if self.custom_data:
2023-04-21 21:44:05 +02:00
metadata = strip.vsetb_strip_settings.metadata.to_dict()
description = strip.vsetb_strip_settings.description
tracker.update_data(shot, metadata, frames=strip.frame_final_duration, description=description)
if self.casting:
casting = [{'asset_id': a.id, 'nb_occurences': a.instance} for a in strip.vsetb_strip_settings.casting]
tracker.update_casting(shot, casting)
2023-04-20 00:12:39 +02:00
return {"FINISHED"}
2023-03-16 18:32:17 +01:00
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'}
2023-03-17 22:55:06 +01:00
class VSETB_OT_import_files(Operator):
2023-04-14 18:55:00 +02:00
bl_idname = "vse_toolbox.import_files"
2023-03-16 18:32:17 +01:00
bl_label = "Import"
bl_description = "Import Edit"
2023-03-14 13:38:04 +01:00
bl_options = {"REGISTER", "UNDO"}
directory : StringProperty(subtype='DIR_PATH')
filepath: StringProperty(
name="File Path",
description="Filepath used for importing the file",
maxlen=1024,
subtype='FILE_PATH',
)
files : CollectionProperty(type=bpy.types.OperatorFileListElement)
2023-03-16 18:32:17 +01:00
clean_sequencer : BoolProperty(
name="Clean Sequencer",
default=False,
description="Clean all existing strips in sequencer",
2023-03-14 13:38:04 +01:00
)
2023-03-16 18:32:17 +01:00
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)
2023-03-14 13:38:04 +01:00
@classmethod
def poll(cls, context):
return True
2023-03-16 18:32:17 +01:00
def draw(self, context):
scn = context.scene
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
2023-03-16 18:32:17 +01:00
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')
2023-03-17 22:55:06 +01:00
sub = row.row(align=True)
2023-03-16 18:32:17 +01:00
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')
2023-03-14 13:38:04 +01:00
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
def execute(self, context):
otio = install_module('opentimelineio')
2023-03-16 18:32:17 +01:00
edit_filepath = Path(self.directory, self.edit)
if not edit_filepath.exists():
self.import_edit = False
2023-03-14 13:38:04 +01:00
2023-03-16 18:32:17 +01:00
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"}
2023-03-14 13:38:04 +01:00
2023-03-17 20:03:38 +01:00
class VSETB_OT_load_assets(Operator):
bl_idname = "vse_toolbox.load_assets"
bl_label = "Load Assets for current projects"
bl_description = "Load Assets"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
2023-03-17 20:03:38 +01:00
if settings.active_project:
return True
def execute(self, context):
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
2023-03-17 20:03:38 +01:00
prefs = get_addon_prefs()
tracker = prefs.tracker
2023-03-21 18:33:29 +01:00
tracker.connect()
2023-03-17 20:03:38 +01:00
project = settings.active_project
2023-03-21 18:33:29 +01:00
project.assets.clear()
2023-03-17 20:03:38 +01:00
assets = tracker.get_assets(project['id'])
if not assets:
self.report({'ERROR'}, f'No Assets found for {project.name}.')
2023-03-20 19:05:22 +01:00
2023-03-21 18:33:29 +01:00
for asset_data in assets:
2023-03-17 20:03:38 +01:00
asset = project.assets.add()
2023-03-20 19:05:22 +01:00
2023-03-21 18:33:29 +01:00
asset.name = asset_data['id']
2023-03-20 19:05:22 +01:00
asset.norm_name = norm_name(asset_data['name'], separator=' ', format=str.lower)
2023-03-21 18:33:29 +01:00
2023-03-20 19:05:22 +01:00
asset.tracker_name = asset_data['name']
2023-03-17 20:03:38 +01:00
asset.id = asset_data['id']
asset.asset_type = asset_data['asset_type']
2023-03-21 18:33:29 +01:00
2023-04-22 15:42:38 +02:00
#for key, value in asset_data.get('data', {}).items():
asset['metadata'] = asset_data.get('data', {})
2023-03-21 18:33:29 +01:00
2023-03-20 19:05:22 +01:00
preview_id = asset_data.get('preview_file_id')
if preview_id:
asset.preview = preview_id
2023-03-21 18:33:29 +01:00
preview_path = Path(PREVIEWS_DIR / project.id / preview_id).with_suffix('.png')
2023-03-20 19:05:22 +01:00
tracker.download_preview(preview_id, preview_path)
if preview_id not in ASSET_PREVIEWS:
ASSET_PREVIEWS.load(preview_id, preview_path.as_posix(), 'IMAGE', True)
2023-03-20 19:05:22 +01:00
2023-03-17 20:03:38 +01:00
self.report({'INFO'}, f'Assets for {project.name} successfully loaded')
return {"FINISHED"}
2023-03-14 13:38:04 +01:00
class VSETB_OT_load_projects(Operator):
bl_idname = "vse_toolbox.load_projects"
bl_label = "Load Projects"
bl_description = "Load Projects"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return True
def execute(self, context):
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
2023-03-14 13:38:04 +01:00
prefs = get_addon_prefs()
tracker = prefs.tracker
2023-04-20 18:16:34 +02:00
old_project_name = settings.project_name.replace(' ', '_').upper()
2023-03-14 13:38:04 +01:00
settings.projects.clear()
tracker.connect()
for project_data in tracker.get_projects():
project = settings.projects.add()
2023-04-19 10:37:38 +02:00
project.type = project_data['production_type'].upper().replace(' ', '')
2023-03-14 13:38:04 +01:00
project.name = project_data['name']
project.id = project_data['id']
2023-04-19 10:37:38 +02:00
if project.type == 'TVSHOW':
2023-04-14 18:55:00 +02:00
for episode_data in tracker.get_episodes(project_data):
episode = project.episodes.add()
episode.name = episode_data['name']
episode.id = episode_data['id']
2023-03-17 20:03:38 +01:00
2023-04-22 15:42:38 +02:00
for metadata_data in tracker.get_metadata_types(project_data):
#pprint(metadata_data)
metadata_type = project.metadata_types.add()
2023-04-21 21:44:05 +02:00
metadata_type.name = metadata_data['name']
metadata_type.field_name = metadata_data['field_name']
2023-04-14 18:55:00 +02:00
metadata_type['choices'] = metadata_data['choices']
2023-04-22 15:42:38 +02:00
metadata_type['entity_type'] = metadata_data['entity_type'].upper()
2023-04-20 00:12:39 +02:00
for status_data in tracker.get_task_statuses(project_data):
#print(metadata_data)
task_status = project.task_statuses.add()
task_status.name = status_data['short_name'].upper()
for task_type_data in tracker.get_shot_task_types(project_data):
task_type = project.task_types.add()
task_type.name = task_type_data['name']
2023-04-04 12:21:45 +02:00
for asset_type_data in tracker.get_asset_types(project_data):
2023-04-14 18:55:00 +02:00
asset_type = project.asset_types.add()
asset_type.name = asset_type_data['name']
2023-04-21 21:44:05 +02:00
project.set_spreadsheet()
2023-04-20 18:16:34 +02:00
for project in settings.projects:
2023-04-21 21:44:05 +02:00
#print(project.name, project.name.replace(' ', '_').upper(), old_project_name)
2023-04-20 18:16:34 +02:00
if project.name.replace(' ', '_').upper() == old_project_name:
2023-04-21 21:44:05 +02:00
#print('Restore Project Name')
2023-04-20 18:16:34 +02:00
settings.project_name = project.name
#else:
# print('Could Not restore Project Name')
#if settings.active_project:
# settings.active_project.set_strip_metadata()
2023-04-20 18:16:34 +02:00
#bpy.ops.vse_toolbox.load_assets()
2023-04-14 18:55:00 +02:00
2023-04-22 15:42:38 +02:00
self.report({"INFO"}, 'Successfully Load Tracker Projects')
2023-03-16 18:32:17 +01:00
return {'FINISHED'}
2023-03-14 13:38:04 +01:00
class VSETB_OT_new_episode(Operator):
bl_idname = "vse_toolbox.new_episode"
bl_label = "New Epispde"
bl_description = "Add new Episode to Project"
bl_options = {"REGISTER", "UNDO"}
episode_name : StringProperty(name="Episode Name", default="")
@classmethod
def poll(cls, context):
return True
def invoke(self, context, event):
scn = context.scene
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
2023-03-14 13:38:04 +01:00
return context.window_manager.invoke_props_dialog(self)
def execute(self, context):
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
2023-03-14 13:38:04 +01:00
prefs = get_addon_prefs()
tracker = prefs.tracker
episode_name = settings.episode_template.format(index=int(self.episode_name))
2023-04-21 21:44:05 +02:00
#print(self.episode_name)
#print('episode_name: ', episode_name)
2023-03-14 13:38:04 +01:00
episode = tracker.get_episode(episode_name)
if episode:
self.report({'ERROR'}, f'Episode {episode_name} already exists')
return {"CANCELLED"}
tracker.new_episode(episode_name)
# tracker.get_episodes
tracker.update_project()
self.report({'INFO'}, f'Episode {episode_name} successfully created')
return {'FINISHED'}
class VSETB_OT_reload_addon(Operator):
bl_idname = "vse_toolbox.reload_addon"
bl_options = {"UNDO"}
bl_label = 'Reload VSE ToolBox Addon'
bl_description = 'Reload The VSE ToolBox Addon'
def execute(self, context):
print('Execute reload')
vse_toolbox.unregister()
importlib.reload(vse_toolbox)
vse_toolbox.register()
return {'FINISHED'}
class VSETB_OT_rename(Operator):
2023-04-14 18:55:00 +02:00
bl_idname = "vse_toolbox.strips_rename"
2023-03-14 13:38:04 +01:00
bl_label = "Rename Strips"
bl_description = "Rename Strips"
bl_options = {"REGISTER", "UNDO"}
template : StringProperty(name="Strip Template Name", default="")
increment : IntProperty(name="Name Increment", default=0)
channel_name : StringProperty(name="Channel Name", default="")
2023-04-14 18:55:00 +02:00
#selected_only : BoolProperty(name="Selected Only", default=False)
2023-03-14 13:38:04 +01:00
start_number : IntProperty(name="Start Number", default=0, min=0)
by_sequence : BoolProperty(
name="Reset By Sequence",
description="Reset Start Number for each sequence",
default=False
)
@classmethod
def poll(cls, context):
return True
def invoke(self, context, event):
scn = context.scene
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
2023-03-14 13:38:04 +01:00
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
scn = context.scene
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
2023-03-14 13:38:04 +01:00
2023-03-16 18:32:17 +01:00
layout = self.layout
col = layout.column()
2023-03-14 13:38:04 +01:00
col.use_property_split = True
col.prop(self, 'template')
col.prop(self, 'start_number')
if self.channel_name == 'Shots':
col.prop(self, 'by_sequence')
2023-04-14 18:55:00 +02:00
#col.prop(self, 'selected_only')
2023-03-14 13:38:04 +01:00
def execute(self, context):
scn = context.scene
2023-04-14 18:55:00 +02:00
#strips = get_strips(channel=self.channel_name, selected_only=self.selected_only)
strips = get_strips(channel=self.channel_name, selected_only=True)
2023-03-14 13:38:04 +01:00
rename_strips(
strips, self.template,
increment=self.increment, start_number=self.start_number,
by_sequence=self.by_sequence
)
return {"FINISHED"}
class VSETB_OT_render(Operator):
2023-04-14 18:55:00 +02:00
bl_idname = "vse_toolbox.strips_render"
2023-03-14 13:38:04 +01:00
bl_label = "Render Shots Strips"
bl_description = "Render Shots Strips"
bl_options = {"REGISTER", "UNDO"}
2023-04-20 00:12:39 +02:00
#selected_only : BoolProperty(name="Selected Only", default=False)
2023-03-14 13:38:04 +01:00
@classmethod
def poll(cls, context):
2023-04-20 00:12:39 +02:00
settings = get_scene_settings()
return settings.active_project
2023-03-14 13:38:04 +01:00
def invoke(self, context, event):
scn = context.scene
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
2023-03-14 13:38:04 +01:00
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
scn = context.scene
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
2023-03-14 13:38:04 +01:00
2023-03-16 18:32:17 +01:00
layout = self.layout
col = layout.column()
2023-03-14 13:38:04 +01:00
col.use_property_split = True
col.use_property_decorate = False
2023-04-20 00:12:39 +02:00
#col.prop(settings, 'channel', text='Channel')
#col.prop(self, 'selected_only')
col.prop(settings.active_project, "render_template")
2023-03-14 13:38:04 +01:00
def execute(self, context):
2023-03-16 18:32:17 +01:00
scn = context.scene
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
2023-04-20 00:12:39 +02:00
strips = get_strips(channel='Shots', selected_only=True)
2023-03-14 13:38:04 +01:00
2023-04-20 00:12:39 +02:00
start_time = time.perf_counter()
render_strips(strips, settings.active_project.render_template)
self.report({"INFO"}, f'Strips rendered in {time.perf_counter()-start_time} seconds')
2023-03-14 13:38:04 +01:00
return {"FINISHED"}
2023-04-14 18:55:00 +02:00
class VSETB_OT_set_sequencer(Operator):
bl_idname = "vse_toolbox.set_sequencer"
bl_label = "Set Sequencer"
bl_description = "Set resolution, frame end and channel names"
2023-03-14 13:38:04 +01:00
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return True
def execute(self, context):
scn = context.scene
set_channels()
movie = get_strips(channel='Movie')
if movie:
movie = movie[0]
movie.transform.scale_x = movie.transform.scale_y = 1
elem = movie.strip_elem_from_frame(scn.frame_current)
scn.render.resolution_x = elem.orig_width
scn.render.resolution_y = elem.orig_height
else:
self.report({'INFO'}, f'Cannot set Resolution. No Movie Found.')
2023-04-20 00:12:39 +02:00
scn.view_settings.view_transform = 'Standard'
scn.render.image_settings.file_format = 'FFMPEG'
scn.render.ffmpeg.gopsize = 5
scn.render.ffmpeg.constant_rate_factor = 'HIGH'
scn.render.ffmpeg.format = 'QUICKTIME'
scn.render.ffmpeg.audio_codec = 'AAC'
scn.render.ffmpeg.audio_codec = 'MP3'
scn.render.ffmpeg.audio_mixrate = 44100
scn.render.ffmpeg.audio_bitrate = 128
2023-03-14 13:38:04 +01:00
return {"FINISHED"}
2023-03-21 18:33:29 +01:00
def get_asset_items(self, context):
settings = get_scene_settings()
project = settings.active_project
return [(a.id, a.label, '', i) for i, a in enumerate(project.assets)]
2023-04-14 18:55:00 +02:00
2023-03-17 20:03:38 +01:00
class VSETB_OT_casting_add(Operator):
bl_idname = "vse_toolbox.casting_add"
bl_label = "Casting Add"
bl_description = "Add Asset to Castin"
bl_options = {"REGISTER", "UNDO"}
bl_property = "asset_name"
2023-03-21 18:33:29 +01:00
asset_name : EnumProperty(name='', items=get_asset_items)
2023-03-17 20:03:38 +01:00
@classmethod
def poll(cls, context):
active_strip = context.scene.sequence_editor.active_strip
if active_strip:
return True
2023-03-21 18:33:29 +01:00
2023-03-17 20:03:38 +01:00
def invoke(self, context, event):
context.window_manager.invoke_search_popup(self)
2023-03-21 18:33:29 +01:00
return {'FINISHED'}
2023-03-17 20:03:38 +01:00
def execute(self, context):
scn = context.scene
active_strip = scn.sequence_editor.active_strip
2023-03-21 18:33:29 +01:00
settings = get_scene_settings()
strip_settings = get_strip_settings()
2023-03-17 20:03:38 +01:00
project = settings.active_project
2023-03-21 18:33:29 +01:00
if strip_settings.casting.get(self.asset_name):
asset = project.assets[self.asset_name]
self.report({'WARNING'}, f"Asset {asset.label} already casted.")
2023-03-17 20:03:38 +01:00
return {"CANCELLED"}
2023-03-21 18:33:29 +01:00
item = strip_settings.casting.add()
asset = project.assets[self.asset_name]
item.name = asset.name
item.id = project.assets[self.asset_name].id
2023-03-17 20:03:38 +01:00
2023-04-14 18:55:00 +02:00
item['_name'] = asset.label
2023-03-21 18:33:29 +01:00
strip_settings.casting.update()
2023-03-17 20:03:38 +01:00
return {"FINISHED"}
2023-03-21 18:33:29 +01:00
class VSETB_OT_casting_remove(Operator):
bl_idname = "vse_toolbox.casting_remove"
bl_label = "Remove Item from Casting"
bl_description = "Remove Item from Casting"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
active_strip = context.scene.sequence_editor.active_strip
if active_strip:
return True
def invoke(self, context, event):
scn = context.scene
strip_settings = get_strip_settings()
idx = strip_settings.casting_index
try:
item = strip_settings.casting[idx]
except IndexError:
pass
else:
item = strip_settings.casting[strip_settings.casting_index]
2023-04-14 18:55:00 +02:00
label = item.asset.label if item.asset.label else 'Empty'
info = f"Item {label} removed from casting"
2023-03-21 18:33:29 +01:00
strip_settings.casting.remove(idx)
if strip_settings.casting_index == 0:
strip_settings.casting_index = 0
else:
strip_settings.casting_index -= 1
self.report({'INFO'}, info)
return {"FINISHED"}
class VSETB_OT_casting_move(Operator):
bl_idname = "vse_toolbox.casting_move"
2023-04-21 21:44:05 +02:00
bl_label = "Move Casting items"
bl_description = "Move Casting items"
2023-03-17 20:03:38 +01:00
bl_options = {"REGISTER", "UNDO"}
2023-04-21 21:44:05 +02:00
direction: EnumProperty(
2023-03-17 20:03:38 +01:00
items=(
('UP', "Up", ""),
('DOWN', "Down", ""),
)
)
asset_name : StringProperty()
@classmethod
def poll(cls, context):
active_strip = context.scene.sequence_editor.active_strip
if active_strip:
return True
2023-04-21 21:44:05 +02:00
def execute(self, context):
2023-03-17 20:03:38 +01:00
scn = context.scene
2023-03-21 18:33:29 +01:00
strip_settings = get_strip_settings()
idx = strip_settings.casting_index
2023-03-17 20:03:38 +01:00
try:
2023-03-21 18:33:29 +01:00
item = strip_settings.casting[idx]
2023-03-17 20:03:38 +01:00
except IndexError:
pass
else:
2023-04-21 21:44:05 +02:00
if self.direction == 'DOWN' and idx < len(strip_settings.casting) - 1:
2023-03-21 18:33:29 +01:00
item_next = strip_settings.casting[idx+1].name
strip_settings.casting.move(idx, idx+1)
strip_settings.casting_index += 1
2023-03-17 20:03:38 +01:00
2023-04-21 21:44:05 +02:00
elif self.direction == 'UP' and idx >= 1:
2023-03-21 18:33:29 +01:00
item_prev = strip_settings.casting[idx-1].name
strip_settings.casting.move(idx, idx-1)
strip_settings.casting_index -= 1
info = f"Item {item.asset.label} moved to position {(item.asset.label, strip_settings.casting_index + 1)}"
self.report({'INFO'}, info)
2023-03-17 20:03:38 +01:00
2023-03-21 18:33:29 +01:00
return {"FINISHED"}
2023-03-17 20:03:38 +01:00
2023-04-21 21:44:05 +02:00
class VSETB_OT_spreadsheet_move(Operator):
bl_idname = "vse_toolbox.spreadsheet_move"
bl_label = "Move Spreadsheet items"
bl_description = "Move Spreadsheet items"
bl_options = {"REGISTER", "UNDO"}
direction: EnumProperty(
items=(
('UP', "Up", ""),
('DOWN', "Down", ""),
)
)
def execute(self, context):
scn = context.scene
project = get_scene_settings().active_project
idx = project.spreadsheet_index
try:
item = project.spreadsheet[idx]
except IndexError:
pass
else:
if self.direction == 'DOWN' and idx < len(project.spreadsheet) - 1:
item_next = project.spreadsheet[idx+1].name
project.spreadsheet.move(idx, idx+1)
project.spreadsheet_index += 1
elif self.direction == 'UP' and idx >= 1:
item_prev = project.spreadsheet[idx-1].name
project.spreadsheet.move(idx, idx-1)
project.spreadsheet_index -= 1
return {"FINISHED"}
2023-03-21 18:33:29 +01:00
class VSETB_OT_copy_casting(Operator):
2023-04-14 18:55:00 +02:00
bl_idname = "vse_toolbox.copy_casting"
2023-04-21 21:44:05 +02:00
bl_label = "Copy Casting"
2023-03-21 18:33:29 +01:00
bl_description = "Copy Casting from active strip"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
active_strip = context.scene.sequence_editor.active_strip
strip_settings = get_strip_settings()
if active_strip and strip_settings.casting:
return True
2023-03-17 20:03:38 +01:00
2023-03-21 18:33:29 +01:00
def execute(self, context):
scn = context.scene
active_strip = scn.sequence_editor.active_strip
strip_settings = get_strip_settings()
datas = [c.to_dict() for c in strip_settings.casting]
CASTING_BUFFER.write_text(json.dumps(datas), encoding='utf-8')
2023-03-17 20:03:38 +01:00
return {"FINISHED"}
2023-03-21 18:33:29 +01:00
class VSETB_OT_paste_casting(Operator):
2023-04-14 18:55:00 +02:00
bl_idname = "vse_toolbox.paste_casting"
2023-04-21 21:44:05 +02:00
bl_label = "Paste Casting"
bl_description = "Paste Casting to active strip"
2023-03-21 18:33:29 +01:00
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
active_strip = context.scene.sequence_editor.active_strip
if active_strip:
return True
def execute(self, context):
scn = context.scene
strip_settings = get_strip_settings()
if not CASTING_BUFFER.exists():
self.report({'ERROR'}, f'No Casting to copy.')
return {"CANCELLED"}
casting_datas = json.loads(CASTING_BUFFER.read_text())
for casting_item in casting_datas:
for strip in context.selected_sequences:
strip_settings = strip.vsetb_strip_settings
if strip_settings.casting.get(casting_item['name']):
continue
item = strip.vsetb_strip_settings.casting.add()
for k, v in casting_item.items():
setattr(item, k, v)
strip_settings.casting.update()
return {"FINISHED"}
2023-04-14 18:55:00 +02:00
class VSETB_OT_set_stamps(Operator):
bl_idname = "vse_toolbox.set_stamps"
bl_label = "Set Stamps"
bl_description = "Set Stamps on Video"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
scn = context.scene
settings = get_scene_settings()
#strip_settings = get_strip_settings()
channel_index = get_channel_index('Stamps')
for strip in get_strips('Stamps'):
if strip.type == 'META':
scn.sequence_editor.sequences.remove(strip)
bpy.ops.sequencer.select_all(action='DESELECT')
2023-04-21 21:44:05 +02:00
crop_x = int(scn.render.resolution_x * 0.4)
crop_max_y = int(scn.render.resolution_y * 0.96)
crop_min_y = int(scn.render.resolution_y * 0.01)
stamp_params = dict(start=scn.frame_start, end=scn.frame_end,
font_size=22, y=0.015, box_margin=0.005, select=True, box_color=(0, 0, 0, 0.5))
2023-04-20 00:12:39 +02:00
2023-04-14 18:55:00 +02:00
# Project Name
2023-04-21 21:44:05 +02:00
project_strip_stamp = new_text_strip('project_name_stamp', channel=1, **stamp_params,
text=settings.active_project.name, x=0.01, align_x='LEFT', align_y='BOTTOM')
2023-04-14 18:55:00 +02:00
2023-04-20 00:12:39 +02:00
project_strip_stamp.crop.max_x = crop_x * 2
2023-04-21 21:44:05 +02:00
project_strip_stamp.crop.max_y = crop_max_y
project_strip_stamp.crop.min_y = crop_min_y
2023-04-20 00:12:39 +02:00
2023-04-14 18:55:00 +02:00
# Shot Name
2023-04-21 21:44:05 +02:00
shot_strip_stamp = new_text_strip('shot_name_stamp', channel=2, **stamp_params,
text='{active_shot_name}', align_y='BOTTOM')
2023-04-14 18:55:00 +02:00
2023-04-20 00:12:39 +02:00
shot_strip_stamp.crop.min_x = crop_x
shot_strip_stamp.crop.max_x = crop_x
2023-04-21 21:44:05 +02:00
shot_strip_stamp.crop.max_y = crop_max_y
shot_strip_stamp.crop.min_y = crop_min_y
2023-04-20 00:12:39 +02:00
2023-04-14 18:55:00 +02:00
# Frame Range
2023-04-21 21:44:05 +02:00
frame_strip_stamp = new_text_strip('frame_range_stamp', channel=3, **stamp_params,
text='{active_shot_frame} / {active_shot_duration}', x=0.99, align_x='RIGHT', align_y='BOTTOM')
2023-04-14 18:55:00 +02:00
2023-04-21 21:44:05 +02:00
frame_strip_stamp.crop.min_x = crop_x *2
frame_strip_stamp.crop.max_y = crop_max_y
frame_strip_stamp.crop.min_y = crop_min_y
2023-04-14 18:55:00 +02:00
bpy.ops.sequencer.meta_make()
stamps_strip = context.active_sequence_strip
stamps_strip.name = 'Stamps'
stamps_strip.channel = channel_index
#stamps_strip = scn.sequence_editor.sequences.new_meta('Stamps', scn.frame_start, scn.frame_end)
#stamps_strip.channel = get_channel_index('Stamps')
scn.frame_set(scn.frame_current) # For update stamps
return {"FINISHED"}
classes = (
2023-03-16 18:32:17 +01:00
VSETB_OT_auto_select_files,
2023-03-17 20:03:38 +01:00
VSETB_OT_casting_add,
2023-03-21 18:33:29 +01:00
VSETB_OT_casting_remove,
VSETB_OT_casting_move,
2023-04-21 21:44:05 +02:00
VSETB_OT_spreadsheet_move,
2023-03-21 18:33:29 +01:00
VSETB_OT_copy_casting,
VSETB_OT_paste_casting,
2023-04-21 21:44:05 +02:00
VSETB_OT_export_spreadsheet,
2023-03-17 22:55:06 +01:00
VSETB_OT_import_files,
2023-03-17 20:03:38 +01:00
VSETB_OT_load_assets,
2023-03-14 13:38:04 +01:00
VSETB_OT_load_projects,
VSETB_OT_new_episode,
VSETB_OT_reload_addon,
VSETB_OT_rename,
VSETB_OT_render,
2023-04-14 18:55:00 +02:00
VSETB_OT_set_sequencer,
2023-03-24 11:42:17 +01:00
VSETB_OT_tracker_connect,
2023-04-20 00:12:39 +02:00
VSETB_OT_set_stamps,
VSETB_OT_upload_to_tracker
2023-03-14 13:38:04 +01:00
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)