vse_toolbox/operators/tracker.py

502 lines
18 KiB
Python
Raw Permalink Normal View History

2023-05-02 18:38:16 +02:00
2023-05-19 22:29:36 +02:00
import os
2024-03-14 17:44:05 +01:00
from os.path import expandvars
2023-05-02 18:38:16 +02:00
from pathlib import Path
2024-03-14 17:44:05 +01:00
from pprint import pprint
2024-04-10 16:15:57 +02:00
import webbrowser
from functools import partial
2023-05-02 18:38:16 +02:00
import bpy
from bpy.types import (Operator, )
from bpy.props import (BoolProperty, EnumProperty, StringProperty)
2023-05-31 12:52:35 +02:00
from vse_toolbox.constants import (ASSET_PREVIEWS, PREVIEWS_DIR, ASSET_ITEMS)
2023-05-02 18:38:16 +02:00
2024-04-16 14:28:02 +02:00
from vse_toolbox.sequencer_utils import (get_strips, get_strip_render_path, get_strip_sequence_name,
render_strip, get_render_attributes)
2023-05-02 18:38:16 +02:00
from vse_toolbox.bl_utils import (get_addon_prefs, get_scene_settings)
2024-04-16 14:28:02 +02:00
from vse_toolbox.file_utils import (norm_name, expand)
2023-05-31 12:52:35 +02:00
from bpy.app.handlers import persistent
2023-05-02 18:38:16 +02: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()
2024-04-16 18:28:36 +02:00
self.report({'INFO'}, f'Successfully login to {settings.tracker_name.title()}')
2023-05-02 18:38:16 +02:00
return {"FINISHED"}
except Exception as e:
print('e: ', e)
2024-04-16 18:28:36 +02:00
self.report({'ERROR'}, f'Cannot connect to tracker, check login and password')
2023-05-02 18:38:16 +02:00
return {"CANCELLED"}
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):
settings = get_scene_settings()
if settings.active_project:
return True
def execute(self, context):
settings = get_scene_settings()
prefs = get_addon_prefs()
tracker = prefs.tracker
2024-04-10 16:15:57 +02:00
tracker.admin_connect()
2023-05-02 18:38:16 +02:00
project = settings.active_project
project.assets.clear()
assets = tracker.get_assets(project['id'])
if not assets:
self.report({'ERROR'}, f'No Assets found for {project.name}.')
for asset_data in assets:
asset = project.assets.add()
asset.name = asset_data['id']
asset.norm_name = norm_name(asset_data['name'], separator=' ', format=str.lower)
asset.tracker_name = asset_data['name']
asset.id = asset_data['id']
asset.asset_type = asset_data['asset_type']
#for key, value in asset_data.get('data', {}).items():
asset['metadata'] = asset_data.get('data', {})
preview_id = asset_data.get('preview_file_id')
if preview_id:
asset.preview = preview_id
preview_path = Path(PREVIEWS_DIR / project.id / preview_id).with_suffix('.png')
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-05-31 12:52:35 +02:00
set_asset_items()
2023-05-02 18:38:16 +02:00
self.report({'INFO'}, f'Assets for {project.name} successfully loaded')
return {"FINISHED"}
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
2024-02-14 14:33:54 +01:00
@staticmethod
def hex_to_rgb(hex_value):
print(hex_value)
b = (hex_value & 0xFF) / 255.0
g = ((hex_value >> 8) & 0xFF) / 255.0
r = ((hex_value >> 16) & 0xFF) / 255.0
return r, g, b
@staticmethod
def hex_to_rgb(color_str):
# supports '123456', '#123456' and '0x123456'
(r,g,b), a = map(lambda component: component / 255, bytes.fromhex(color_str[-6:])), 1.0
return (r,g,b,a)
2024-04-16 14:28:02 +02:00
def invoke(self, context, event):
self.ctrl = event.ctrl
return self.execute(context)
2023-05-02 18:38:16 +02:00
def execute(self, context):
settings = get_scene_settings()
prefs = get_addon_prefs()
tracker = prefs.tracker
2024-02-14 14:33:54 +01:00
prev_project_name = settings.project_name
2023-05-02 18:38:16 +02:00
2024-04-10 16:15:57 +02:00
tracker.admin_connect()
2023-05-02 18:38:16 +02:00
2023-05-31 12:52:35 +02:00
project_datas = tracker.get_projects()
2024-02-14 11:19:17 +01:00
for project_data in sorted(project_datas, key=lambda x: x['name']):
2023-05-31 12:52:35 +02:00
project = settings.projects.get(project_data['name'])
if not project:
project = settings.projects.add()
2023-05-02 18:38:16 +02:00
project.type = project_data['production_type'].upper().replace(' ', '')
project.name = project_data['name']
project.id = project_data['id']
if project.type == 'TVSHOW':
2023-05-31 12:52:35 +02:00
episode_datas = tracker.get_episodes(project_data)
for episode_data in episode_datas:
episode = project.episodes.get(episode_data['name'])
if not episode:
episode = project.episodes.add()
2023-05-02 18:38:16 +02:00
episode.name = episode_data['name']
episode.id = episode_data['id']
2023-05-31 12:52:35 +02:00
# Clear deleted episodes
ep_names = [e['name'] for e in episode_datas]
for ep in reversed(project.episodes):
if ep.name not in ep_names:
project.episodes.remove(list(project.episodes).index(ep))
2024-04-10 16:15:57 +02:00
else:
# Add sequences
sequences_data = tracker.get_sequences(project_data)
for sequence_data in sequences_data:
sequence = project.sequences.get(sequence_data['name'])
if not sequence:
sequence = project.sequences.add()
sequence.name = sequence_data['name']
sequence.id = sequence_data['id']
2023-05-02 18:38:16 +02:00
2023-05-31 14:31:53 +02:00
project.metadata_types.clear()
2024-04-16 18:28:36 +02:00
2023-05-02 18:38:16 +02:00
for metadata_data in tracker.get_metadata_types(project_data):
#pprint(metadata_data)
metadata_type = project.metadata_types.add()
metadata_type.name = metadata_data['name']
metadata_type.field_name = metadata_data['field_name']
2024-03-14 17:44:05 +01:00
metadata_type.data_type = metadata_data['data_type']
2023-05-02 18:38:16 +02:00
#metadata_type['choices'] = metadata_data['choices']
if prefs.sort_metadata_items:
metadata_data['choices'].sort()
for choice in metadata_data['choices']:
choice_item = metadata_type.choices.add()
choice_item.name = choice
metadata_type['entity_type'] = metadata_data['entity_type'].upper()
2023-05-31 14:31:53 +02:00
project.task_statuses.clear()
2023-05-02 18:38:16 +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()
2023-05-31 14:31:53 +02:00
project.task_types.clear()
2023-05-02 18:38:16 +02:00
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']
2024-04-10 16:15:57 +02:00
task_type.id = task_type_data['id']
2024-02-14 14:33:54 +01:00
task_type.color = self.hex_to_rgb(task_type_data['color'])[:3]
project.set_shot_tasks()
2023-05-02 18:38:16 +02:00
2023-05-31 14:31:53 +02:00
project.asset_types.clear()
2023-05-02 18:38:16 +02:00
for asset_type_data in tracker.get_asset_types(project_data):
asset_type = project.asset_types.add()
asset_type.name = asset_type_data['name']
project.set_spreadsheet()
2023-05-31 12:52:35 +02:00
# Remove deleted projects
project_names = [p['name'] for p in project_datas]
for project in reversed(settings.projects):
if project.name not in project_names:
settings.projects.remove(list(settings.projects).index(project))
2023-05-04 12:24:48 +02:00
2024-04-16 14:28:02 +02:00
if self.ctrl or not settings.get('projects_loaded'):
bpy.ops.vse_toolbox.load_settings()
2023-05-02 18:38:16 +02:00
2024-04-10 16:15:57 +02:00
if prev_project_name != '/' and prev_project_name in settings.projects:
settings.project_name = prev_project_name
2023-05-02 18:38:16 +02:00
#if settings.active_project:
# settings.active_project.set_strip_metadata()
2024-04-16 14:28:02 +02:00
settings['projects_loaded'] = True
2023-05-02 18:38:16 +02:00
self.report({"INFO"}, 'Successfully Load Tracker Projects')
return {'FINISHED'}
class VSETB_OT_new_episode(Operator):
bl_idname = "vse_toolbox.new_episode"
2024-04-10 16:15:57 +02:00
bl_label = "New Episode"
2023-05-02 18:38:16 +02:00
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
settings = get_scene_settings()
return context.window_manager.invoke_props_dialog(self)
def execute(self, context):
settings = get_scene_settings()
prefs = get_addon_prefs()
tracker = prefs.tracker
episode_name = settings.episode_template.format(index=int(self.episode_name))
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.update_project()
2024-04-16 14:28:02 +02:00
2023-05-02 18:38:16 +02:00
self.report({'INFO'}, f'Episode {episode_name} successfully created')
2024-04-16 14:28:02 +02:00
2023-05-02 18:38:16 +02:00
return {'FINISHED'}
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"}
@classmethod
def poll(cls, context):
return True
def invoke(self, context, event):
prefs = get_addon_prefs()
settings = get_scene_settings()
2024-04-10 16:15:57 +02:00
self.project = settings.active_project
2023-05-02 18:38:16 +02:00
tracker = prefs.tracker
tracker.connect()
#self.bl_label = f"Upload to {settings.tracker_name.title()}"
2024-04-10 16:15:57 +02:00
return context.window_manager.invoke_props_dialog(self, width=350)
2023-05-02 18:38:16 +02:00
def draw(self, context):
scn = context.scene
2024-04-10 16:15:57 +02:00
upload_to_tracker = self.project.upload_to_tracker
2023-05-02 18:38:16 +02:00
layout = self.layout
col = layout.column()
col.use_property_split = True
2024-04-10 16:15:57 +02:00
col.use_property_decorate = False
col.prop(upload_to_tracker, 'render_strips', text='Render Strips')
if upload_to_tracker.render_strips:
col.use_property_split = False
col.prop(upload_to_tracker, 'render_strip_template', text='')
col.use_property_split = True
col.separator()
col.prop(upload_to_tracker, 'task', text='Task')
col.prop(upload_to_tracker, 'status', text='Status')
col.prop(upload_to_tracker, 'comment', text='Comment')
2023-05-02 18:38:16 +02:00
row = col.row(heading='Add Preview')
2024-04-10 16:15:57 +02:00
row.prop(upload_to_tracker, 'add_preview', text='')
row.prop(upload_to_tracker, 'preview_mode', text='')
if upload_to_tracker.add_preview and not upload_to_tracker.render_strips:
col.use_property_split = False
col.prop(self.project, 'render_video_strip_template', text='')
col.use_property_split = True
2023-05-02 18:38:16 +02:00
col.separator()
2024-04-10 16:15:57 +02:00
col.prop(upload_to_tracker, 'custom_data', text='Custom Data')
col.prop(upload_to_tracker, 'update_frames', text='Update frames')
col.prop(upload_to_tracker, 'casting', text='Casting')
col.prop(upload_to_tracker, 'tasks_comment', text='Tasks Comment')
col.prop(upload_to_tracker, 'set_main_preview', text='Set Main Preview')
2023-05-02 18:38:16 +02:00
def execute(self, context):
#self.report({'ERROR'}, f'Export not implemented yet.')
prefs = get_addon_prefs()
settings = get_scene_settings()
2024-04-16 14:28:02 +02:00
tracker = prefs.tracker
2024-04-10 16:15:57 +02:00
upload_to_tracker = self.project.upload_to_tracker
2024-04-16 14:28:02 +02:00
render_attrs = get_render_attributes()
project_templates = {t.name: t.value for t in self.project.templates}
2024-04-10 16:15:57 +02:00
2023-05-02 18:38:16 +02:00
episode = None
if settings.active_episode:
episode = settings.active_episode.id
2024-04-10 16:15:57 +02:00
format_data = {**settings.format_data, **self.project.format_data}
2024-04-16 14:28:02 +02:00
2024-04-10 16:15:57 +02:00
status = upload_to_tracker.status
2023-05-02 18:38:16 +02:00
if status == 'CURRENT':
status = None
2024-04-16 14:28:02 +02:00
shot_strips = get_strips(channel='Shots', selected_only=True)
context.window_manager.progress_begin(0, len(shot_strips))
2023-05-02 18:38:16 +02:00
2024-04-16 14:28:02 +02:00
for i, strip in enumerate(shot_strips):
context.window_manager.progress_update(i)
2023-05-19 22:29:36 +02:00
strip_settings = strip.vsetb_strip_settings
2023-05-02 18:38:16 +02:00
sequence_name = get_strip_sequence_name(strip)
shot_name = strip.name
sequence = tracker.get_sequence(sequence_name, episode=episode)
2024-03-14 17:44:05 +01:00
metadata = strip_settings.metadata.to_dict(use_name=False)
2023-05-02 18:38:16 +02:00
#print(metadata)
if not sequence:
self.report({"INFO"}, f'Create sequence {sequence_name} in Kitsu')
sequence = tracker.new_sequence(sequence_name, episode=episode)
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)
2024-04-10 16:15:57 +02:00
task = tracker.get_task(upload_to_tracker.task, entity=shot)
2023-05-02 18:38:16 +02:00
if not task:
2024-04-10 16:15:57 +02:00
task = tracker.new_task(shot, task_type=upload_to_tracker.task)
2023-05-02 18:38:16 +02:00
preview = None
2024-04-10 16:15:57 +02:00
if upload_to_tracker.add_preview:
2023-05-19 22:29:36 +02:00
strip_data = {**format_data, **strip_settings.format_data}
2024-04-10 16:15:57 +02:00
if upload_to_tracker.render_strips:
2024-04-16 14:28:02 +02:00
preview_template = expand(upload_to_tracker.render_strip_template, **project_templates)
strip_data['ext'] = 'mov'
2024-04-10 16:15:57 +02:00
else:
2024-04-22 16:32:26 +02:00
preview_template = expand(self.project.render_video_strip_template, **project_templates)
2024-04-10 16:15:57 +02:00
2024-03-14 17:44:05 +01:00
preview = preview_template.format(**strip_data)
2023-05-19 22:29:36 +02:00
preview = Path(os.path.abspath(bpy.path.abspath(preview)))
2024-04-16 14:28:02 +02:00
if upload_to_tracker.render_strips:
render_strip(strip, preview, attributes=render_attrs)
2023-05-02 18:38:16 +02:00
#print(preview)
if not preview.exists():
print(f'The preview {preview} not exists')
preview = None
elif task.get('last_comment') and task['last_comment']['previews']:
2024-04-10 16:15:57 +02:00
if upload_to_tracker.preview_mode == 'REPLACE':
2023-05-02 18:38:16 +02:00
tracker.remove_comment(task['last_comment'])
2024-04-10 16:15:57 +02:00
elif upload_to_tracker.preview_mode == 'ONLY_NEW':
2023-05-02 18:38:16 +02:00
preview = None
2024-02-15 09:52:55 +01:00
2024-04-10 16:15:57 +02:00
comment_data = None
2024-04-16 14:28:02 +02:00
if status or upload_to_tracker.comment or preview:
2024-04-10 16:15:57 +02:00
comment_data = tracker.new_comment(task, comment=upload_to_tracker.comment, status=status)
if preview:
print('Upload preview from', preview)
preview_data = tracker.new_preview(
task=task,
comment=comment_data,
preview=preview)
if upload_to_tracker.set_main_preview:
bpy.app.timers.register(partial(tracker.set_main_preview, preview_data), first_interval=10)
params = {}
if upload_to_tracker.custom_data:
params['custom_data'] = metadata
params['description'] = strip_settings.description
if upload_to_tracker.update_frames:
params['frames'] = strip.frame_final_duration
2023-05-02 18:38:16 +02:00
2024-04-10 16:15:57 +02:00
if params:
tracker.update_data(shot, **params)
2023-05-02 18:38:16 +02:00
2024-04-10 16:15:57 +02:00
if upload_to_tracker.casting:
2023-05-19 22:29:36 +02:00
casting = [{'asset_id': a.id, 'nb_occurences': a.instance} for a in strip_settings.casting]
2023-05-02 18:38:16 +02:00
tracker.update_casting(shot, casting)
2024-04-10 16:15:57 +02:00
if upload_to_tracker.tasks_comment:
for task_type in self.project.task_types:
2024-02-15 09:52:55 +01:00
2024-03-14 17:44:05 +01:00
task = getattr(strip_settings.tasks, norm_name(task_type.name))
2024-02-15 09:52:55 +01:00
tracker_task = tracker.get_task(task_type.name, entity=shot)
if task.comment and tracker_task.get('last_comment') != task.comment:
2024-03-14 17:44:05 +01:00
tracker.new_comment(tracker_task, comment=task.comment)
2023-05-02 18:38:16 +02:00
2024-04-16 14:28:02 +02:00
context.window_manager.progress_end()
2023-05-02 18:38:16 +02:00
return {"FINISHED"}
2024-04-10 16:15:57 +02:00
class VSETB_OT_open_shot_on_tracker(Operator):
bl_idname = "vse_toolbox.open_shot_on_tracker"
bl_label = "Open Shot on Tracker"
bl_description = "Open Shot on Tracker"
@classmethod
def poll(cls, context):
2024-12-19 10:48:21 +01:00
if not get_strips(channel='Shots', selected_only=True):
cls.poll_message_set('No active Shots strip')
return
2024-04-10 16:15:57 +02:00
prefs = get_addon_prefs()
if prefs.tracker:
return True
def execute(self, context):
prefs = get_addon_prefs()
tracker = prefs.tracker
strips = get_strips(channel='Shots', selected_only=True)
url = tracker.get_shots_search_url([s.name for s in strips])
webbrowser.open_new_tab(url)
return {"FINISHED"}
2024-04-16 14:28:02 +02:00
2023-05-31 12:52:35 +02:00
@persistent
def set_asset_items(scene=None):
ASSET_ITEMS.clear()
settings = get_scene_settings()
if settings.active_project:
ASSET_ITEMS.extend([(a.id, a.label, '', i) for i, a in enumerate(settings.active_project.assets)])
2023-05-02 18:38:16 +02:00
classes = (
VSETB_OT_load_assets,
VSETB_OT_load_projects,
VSETB_OT_new_episode,
VSETB_OT_tracker_connect,
VSETB_OT_upload_to_tracker,
2024-04-10 16:15:57 +02:00
VSETB_OT_open_shot_on_tracker
2023-05-02 18:38:16 +02:00
)
2023-05-31 12:52:35 +02:00
def register():
if not bpy.app.background:
bpy.app.handlers.load_post.append(set_asset_items)
2023-05-02 18:38:16 +02:00
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
2023-05-31 12:52:35 +02:00
bpy.utils.unregister_class(cls)
if not bpy.app.background:
bpy.app.handlers.load_post.remove(set_asset_items)