pull/5/head
Christophe SEUX 2023-04-20 00:12:39 +02:00
parent 93134f25a9
commit d2caf90ea2
8 changed files with 505 additions and 142 deletions

View File

@ -23,6 +23,8 @@ SOUND_SUFFIXES = ['.mp3', '.aaf', '.flac', '.wav']
CONFIG_DIR = Path(appdirs.user_config_dir(__package__.split('.')[0])) CONFIG_DIR = Path(appdirs.user_config_dir(__package__.split('.')[0]))
PREVIEWS_DIR = CONFIG_DIR / 'thumbnails' PREVIEWS_DIR = CONFIG_DIR / 'thumbnails'
TASK_ITEMS = []
ASSET_PREVIEWS = bpy.utils.previews.new() ASSET_PREVIEWS = bpy.utils.previews.new()
CASTING_BUFFER = CONFIG_DIR / 'casting.json' CASTING_BUFFER = CONFIG_DIR / 'casting.json'

View File

@ -4,6 +4,7 @@ import bpy
import importlib import importlib
import json import json
import re import re
import time
import vse_toolbox import vse_toolbox
from bpy_extras.io_utils import ImportHelper from bpy_extras.io_utils import ImportHelper
@ -29,6 +30,7 @@ from vse_toolbox.constants import (
PREVIEWS_DIR, PREVIEWS_DIR,
SOUNDS, SOUNDS,
SOUND_SUFFIXES, SOUND_SUFFIXES,
TASK_ITEMS
) )
from vse_toolbox.sequencer_utils import ( from vse_toolbox.sequencer_utils import (
clean_sequencer, clean_sequencer,
@ -41,8 +43,11 @@ from vse_toolbox.sequencer_utils import (
render_strips, render_strips,
set_channels, set_channels,
get_channel_index, get_channel_index,
new_text_strip new_text_strip,
get_strip_render_path,
get_strip_sequence_name
) )
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.file_utils import install_module, norm_name, norm_str from vse_toolbox.file_utils import install_module, norm_name, norm_str
@ -88,6 +93,133 @@ class VSETB_OT_export_csv(Operator):
return {"CANCELLED"} return {"CANCELLED"}
def get_task_status_items(self, context):
settings = get_scene_settings()
project = settings.active_project
status_items = [('CURRENT', 'Current', '')]
status_items += [(t.name, t.name, '') for t in project.task_statuses]
return status_items
def get_task_type_items(self, context):
settings = get_scene_settings()
project = settings.active_project
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"}
task: EnumProperty(items=get_task_type_items)
status: EnumProperty(items=get_task_status_items)
comment : StringProperty()
add_preview : BoolProperty(default=False)
preview_mode : EnumProperty(items=[(m, m.title().replace('_', ' '), '') for m in ('ONLY_NEW', 'REPLACE', 'ADD')])
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()
#project = settings.active_project
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)
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)
print(task)
tracker.new_comment(task, comment=self.comment, status=status)
#status = tracker.get_task(shot, self.task)
# if self.add_preview:
# strip_movie_path = Path(get_strip_render_path(strip, template))
# if path.exists():
# else:
# self.report({"WARNING"}, f"{strip_movie_path} not exist, skipped")
return {"FINISHED"}
# if add_to_kitsu:
# shot_name = T['shot_tracker_name'].fields['shot'].format(data['shot'])
# seq_name = T['sequence_tracker_name'].fields['sequence'].format(data['sequence'])
# sequence = p.sequences.add(seq_name)
# shot = sequence.shots.add(shot_name)
# animatic_task = shot.tasks.add('animatic')
# animatic_task.comments.new(
# comment=f'Reel edit v{version:03d}',
# status='done',
# preview=movie_output,
# set_main_preview=True
# )
# shot_data = {
# 'fps': int(FPS),
# 'frame_in': strip.frame_final_start,
# 'frame_out': strip.frame_final_end-1,
# 'nb_frames': strip.frame_final_duration
# }
# shot.update_data(shot_data)
# return {"CANCELLED"}
class VSETB_OT_auto_select_files(Operator): class VSETB_OT_auto_select_files(Operator):
bl_idname = "import.auto_select_files" bl_idname = "import.auto_select_files"
bl_label = "Auto Select" bl_label = "Auto Select"
@ -317,8 +449,6 @@ class VSETB_OT_load_projects(Operator):
tracker.connect() tracker.connect()
for project_data in tracker.get_projects(): for project_data in tracker.get_projects():
project = settings.projects.add() project = settings.projects.add()
project.type = project_data['production_type'].upper().replace(' ', '') project.type = project_data['production_type'].upper().replace(' ', '')
project.name = project_data['name'] project.name = project_data['name']
@ -336,6 +466,15 @@ class VSETB_OT_load_projects(Operator):
metadata_type.name = metadata_data['field_name'] metadata_type.name = metadata_data['field_name']
metadata_type['choices'] = metadata_data['choices'] metadata_type['choices'] = metadata_data['choices']
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']
for asset_type_data in tracker.get_asset_types(project_data): for asset_type_data in tracker.get_asset_types(project_data):
asset_type = project.asset_types.add() asset_type = project.asset_types.add()
asset_type.name = asset_type_data['name'] asset_type.name = asset_type_data['name']
@ -465,11 +604,12 @@ class VSETB_OT_render(Operator):
bl_description = "Render Shots Strips" bl_description = "Render Shots Strips"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
selected_only : BoolProperty(name="Selected Only", default=False) #selected_only : BoolProperty(name="Selected Only", default=False)
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return True settings = get_scene_settings()
return settings.active_project
def invoke(self, context, event): def invoke(self, context, event):
scn = context.scene scn = context.scene
@ -485,15 +625,20 @@ class VSETB_OT_render(Operator):
col = layout.column() col = layout.column()
col.use_property_split = True col.use_property_split = True
col.use_property_decorate = False col.use_property_decorate = False
col.prop(settings, 'channel', text='Channel') #col.prop(settings, 'channel', text='Channel')
col.prop(self, 'selected_only') #col.prop(self, 'selected_only')
col.prop(settings.active_project, "render_template")
def execute(self, context): def execute(self, context):
scn = context.scene scn = context.scene
settings = get_scene_settings() settings = get_scene_settings()
strips = get_strips(channel=settings.channel, selected_only=self.selected_only) strips = get_strips(channel='Shots', selected_only=True)
render_strips(strips) 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')
return {"FINISHED"} return {"FINISHED"}
@ -522,6 +667,16 @@ class VSETB_OT_set_sequencer(Operator):
else: else:
self.report({'INFO'}, f'Cannot set Resolution. No Movie Found.') self.report({'INFO'}, f'Cannot set Resolution. No Movie Found.')
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
return {"FINISHED"} return {"FINISHED"}
@ -744,6 +899,9 @@ class VSETB_OT_set_stamps(Operator):
bpy.ops.sequencer.select_all(action='DESELECT') bpy.ops.sequencer.select_all(action='DESELECT')
crop_x = int(scn.render.resolution_x * 0.33)
crop_y = int(scn.render.resolution_y * 0.95)
# Project Name # Project Name
project_strip_stamp = new_text_strip( project_strip_stamp = new_text_strip(
'project_name_stamp', channel=1, start=scn.frame_start, end=scn.frame_end, 'project_name_stamp', channel=1, start=scn.frame_start, end=scn.frame_end,
@ -752,6 +910,9 @@ class VSETB_OT_set_stamps(Operator):
box_color=(0, 0, 0, 0.5), box_margin=0.005, box_color=(0, 0, 0, 0.5), box_margin=0.005,
) )
project_strip_stamp.crop.max_x = crop_x * 2
project_strip_stamp.crop.max_y = crop_y
# Shot Name # Shot Name
shot_strip_stamp = new_text_strip( shot_strip_stamp = new_text_strip(
f'shot_name_stamp', channel=2, start=scn.frame_start, end=scn.frame_end, f'shot_name_stamp', channel=2, start=scn.frame_start, end=scn.frame_end,
@ -760,6 +921,10 @@ class VSETB_OT_set_stamps(Operator):
box_color=(0, 0, 0, 0.5), box_margin=0.005, box_color=(0, 0, 0, 0.5), box_margin=0.005,
) )
shot_strip_stamp.crop.min_x = crop_x
shot_strip_stamp.crop.max_x = crop_x
shot_strip_stamp.crop.max_y = crop_y
# Frame Range # Frame Range
frame_strip_stamp = new_text_strip( frame_strip_stamp = new_text_strip(
'frame_range_stamp', channel=3, start=scn.frame_start, end=scn.frame_end, 'frame_range_stamp', channel=3, start=scn.frame_start, end=scn.frame_end,
@ -768,6 +933,8 @@ class VSETB_OT_set_stamps(Operator):
box_color=(0, 0, 0, 0.5), box_margin=0.005, box_color=(0, 0, 0, 0.5), box_margin=0.005,
) )
frame_strip_stamp.crop.min_x = int(scn.render.resolution_x * 0.66)
frame_strip_stamp.crop.max_y = crop_y
# for shot_strip in get_strips('Shots'): # for shot_strip in get_strips('Shots'):
# # Shot Name # # Shot Name
# shot_strip_stamp = new_text_strip( # shot_strip_stamp = new_text_strip(
@ -806,7 +973,8 @@ classes = (
VSETB_OT_render, VSETB_OT_render,
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
) )
def register(): def register():

View File

@ -86,9 +86,13 @@ class VSETB_PT_sequencer(VSETB_main, Panel):
col.operator('vse_toolbox.set_sequencer', text='Set-Up Sequencer', icon='SEQ_SEQUENCER') col.operator('vse_toolbox.set_sequencer', text='Set-Up Sequencer', icon='SEQ_SEQUENCER')
#row = col.row() #row = col.row()
episode = project.episode_name shot_label = ''
sequence_label = project.sequence_template.format(episode=episode, index=project.sequence_start_number) sequence_label = ''
shot_label = project.shot_template.format(episode=episode, sequence=sequence_label, index=project.shot_start_number)
if project:
episode = project.episode_name
sequence_label = project.sequence_template.format(episode=episode, index=project.sequence_start_number)
shot_label = project.shot_template.format(episode=episode, sequence=sequence_label, index=project.shot_start_number)
#row.separator() #row.separator()
@ -106,19 +110,41 @@ class VSETB_PT_sequencer(VSETB_main, Panel):
row.enabled = False row.enabled = False
op = row.operator('vse_toolbox.strips_rename', text=f'Rename {channel_name} ( {label} )', icon='SORTALPHA') op = row.operator('vse_toolbox.strips_rename', text=f'Rename {channel_name} ( {label} )', icon='SORTALPHA')
op.channel_name = channel_name if project:
if channel_name == 'Shots': op.channel_name = channel_name
op.start_number = project.shot_start_number if channel_name == 'Shots':
op.template = project.shot_template op.start_number = project.shot_start_number
op.increment = project.shot_increment op.template = project.shot_template
else: op.increment = project.shot_increment
op.start_number = project.sequence_start_number else:
op.template = project.sequence_template op.start_number = project.sequence_start_number
op.increment =project.sequence_increment op.template = project.sequence_template
op.increment = project.sequence_increment
col.operator('vse_toolbox.set_stamps', text='Set Stamps', icon='COLOR') col.operator('vse_toolbox.set_stamps', text='Set Stamps', icon='COLOR')
class VSETB_PT_settings(VSETB_main, Panel):
bl_label = "Settings"
bl_parent_id = "VSETB_PT_main"
bl_options = {'DEFAULT_CLOSED'}
#def draw_header_preset(self, context):
# self.layout.operator('vse_toolbox.import_files', icon='IMPORT', text='', emboss=False)
def draw(self, context):
prefs = get_addon_prefs()
layout = self.layout
settings = get_scene_settings()
project = settings.active_project
col = layout.column()
#row = col.row(align=True)
col.prop(project, 'sequence_template')
col.prop(project, 'shot_template')
col.prop(project, 'render_template')
class VSETB_PT_imports(VSETB_main, Panel): class VSETB_PT_imports(VSETB_main, Panel):
bl_label = "Imports" bl_label = "Imports"
bl_parent_id = "VSETB_PT_main" bl_parent_id = "VSETB_PT_main"
@ -154,7 +180,10 @@ class VSETB_PT_exports(VSETB_main, Panel):
# TODO FAIRE DES VRAIS OPS # TODO FAIRE DES VRAIS OPS
layout.operator('vse_toolbox.strips_render', text='Render Strips', icon='SEQUENCE') layout.operator('vse_toolbox.strips_render', text='Render Strips', icon='SEQUENCE')
layout.operator('vse_toolbox.export_csv', text='Export', icon='EXPORT')
tracker_label = settings.tracker_name.title().replace('_', ' ')
layout.operator('vse_toolbox.upload_to_tracker', text=f'Upload to {tracker_label}', icon='EXPORT')
layout.operator('vse_toolbox.export_csv', text='Export csv', icon='SPREADSHEET')
class VSETB_PT_casting(VSETB_main, Panel): class VSETB_PT_casting(VSETB_main, Panel):

View File

@ -48,16 +48,18 @@ def load_trackers():
if not Tracker in obj.__mro__: if not Tracker in obj.__mro__:
continue continue
if obj is Tracker or obj.name in (a.name for a in TRACKERS): if obj is Tracker or name in (a.__name__ for a in TRACKERS):
continue continue
try: try:
print(f'Register Plugin {name}') print(f'Register Tracker {name}')
bpy.utils.register_class(obj) bpy.utils.register_class(obj)
setattr(Trackers, norm_str(obj.name), PointerProperty(type=obj)) #obj.register()
setattr(Trackers, norm_str(name), PointerProperty(type=obj))
TRACKERS.append(obj) TRACKERS.append(obj)
except Exception as e: except Exception as e:
print(f'Could not register library_type {name}') print(f'Could not register Tracker {name}')
print(e) print(e)
def load_tracker_prefs(): def load_tracker_prefs():

View File

@ -41,15 +41,20 @@ def on_project_updated(self, context):
settings = get_scene_settings() settings = get_scene_settings()
settings['episodes'] = 0 settings['episodes'] = 0
print('Update active Project') #print('Update active Project')
bpy.ops.vse_toolbox.load_assets() bpy.ops.vse_toolbox.load_assets()
if settings.active_project: if settings.active_project:
settings.active_project.set_strip_metadata() settings.active_project.set_strip_metadata()
os.environ['TRACKER_PROJECT_ID'] = settings.active_project.id
def on_episode_updated(self, context):
os.environ['TRACKER_EPISODE_ID'] = settings.active_episode.id
def get_tracker_items(self, context): def get_tracker_items(self, context):
return [(norm_str(a.name, format=str.upper), a.name, "", i) for i, a in enumerate(TRACKERS)] return [(norm_str(a.__name__, format=str.upper), a.__name__, "", i) for i, a in enumerate(TRACKERS)]
class Asset(PropertyGroup): class Asset(PropertyGroup):
@ -97,6 +102,14 @@ class MetadataType(PropertyGroup):
choice : EnumProperty(items=lambda s, c: [(c, c.replace(' ', '_').upper(), '') for c in s['choices']]) choice : EnumProperty(items=lambda s, c: [(c, c.replace(' ', '_').upper(), '') for c in s['choices']])
class TaskType(PropertyGroup):
__annotations__ = {}
class TaskStatus(PropertyGroup):
__annotations__ = {}
class Metadata(PropertyGroup): class Metadata(PropertyGroup):
__annotations__ = {} __annotations__ = {}
@ -131,11 +144,17 @@ class Project(PropertyGroup):
shot_template : StringProperty( shot_template : StringProperty(
name="Shot Name", default="{sequence}_sh{index:04d}") name="Shot Name", default="{sequence}_sh{index:04d}")
episode_name : EnumProperty(items=get_episodes_items) render_template : StringProperty(
name="Render Name", default="//render/{strip_name}.{ext}")
episode_name : EnumProperty(items=get_episodes_items, update=on_episode_updated)
episodes : CollectionProperty(type=Episode) episodes : CollectionProperty(type=Episode)
assets : CollectionProperty(type=Asset) assets : CollectionProperty(type=Asset)
asset_types : CollectionProperty(type=AssetType) asset_types : CollectionProperty(type=AssetType)
metadata_types : CollectionProperty(type=MetadataType) metadata_types : CollectionProperty(type=MetadataType)
task_types : CollectionProperty(type=TaskType)
task_statuses : CollectionProperty(type=TaskStatus)
type : StringProperty() type : StringProperty()
def set_strip_metadata(self): def set_strip_metadata(self):
@ -272,9 +291,11 @@ classes=(
Asset, Asset,
AssetCasting, AssetCasting,
AssetType, AssetType,
TaskStatus,
Episode, Episode,
Metadata, Metadata,
MetadataType, MetadataType,
TaskType,
Project, Project,
VSETB_UL_casting, VSETB_UL_casting,
VSETB_PGT_scene_settings, VSETB_PGT_scene_settings,
@ -290,6 +311,7 @@ def load_handler(dummy):
settings = get_scene_settings() settings = get_scene_settings()
if settings.active_project: if settings.active_project:
settings.active_project.set_strip_metadata() settings.active_project.set_strip_metadata()
os.environ['TRACKER_PROJECT_ID'] = settings.active_project.id
def register(): def register():

View File

@ -5,10 +5,10 @@ import re
import urllib3 import urllib3
import traceback import traceback
import time import time
import uuid
from bpy.props import PointerProperty, StringProperty from bpy.props import PointerProperty, StringProperty
from pathlib import Path from pathlib import Path
from vse_toolbox.bl_utils import get_addon_prefs, get_scene_settings
from vse_toolbox.file_utils import install_module, norm_str from vse_toolbox.file_utils import install_module, norm_str
from vse_toolbox.resources.trackers.tracker import Tracker from vse_toolbox.resources.trackers.tracker import Tracker
@ -18,34 +18,163 @@ except Exception as e:
print('Could not install gazu') print('Could not install gazu')
print(e) print(e)
LOGIN = None
class Kitsu(Tracker): class Kitsu(Tracker):
name = "Kitsu"
url: StringProperty() url: StringProperty()
login: StringProperty() login: StringProperty()
password: StringProperty(subtype='PASSWORD') password: StringProperty(subtype='PASSWORD')
project_name = None def connect(self, url=None, login=None, password=None):
'''Connect to kitsu api using provided url, login and password'''
def draw_prefs(self, layout): global LOGIN
layout.prop(self, 'url', text='Url')
layout.prop(self, 'login', text='Login') urllib3.disable_warnings()
layout.prop(self, 'password', text='Password')
if url is None:
url = self.url
if login is None:
login = self.login
if password is None:
password = self.password
if not url:
print(f'Kitsu Url: {self.url} is empty')
return
if login == LOGIN:
return
if not url.endswith('/api'):
url += '/api'
print(f'Info: Setting Host for kitsu {url}')
gazu.client.set_host(url)
if not gazu.client.host_is_up():
print('Error: Kitsu Host is down')
try:
print(f'Info: Log in to kitsu as {login}')
res = gazu.log_in(login, password)
LOGIN = login
print(f'Info: Sucessfully login to Kitsu as {res["user"]["full_name"]}')
return res['user']
except Exception as e:
print(f'Error: {traceback.format_exc()}')
def get_id(self, data):
if isinstance(data, str):
if self.is_id(data):
return data
#return None
elif isinstance(data, dict):
return data['id']
elif data: # Should be a Class
return data.id
def is_id(self, id):
if not isinstance(id, str):
return False
try:
uuid.UUID(id)
return True
except ValueError:
return False
def get_project(self, project=None):
if project:
project_id = self.get_id(project)
if project_id:
return project_id
return gazu.project.get_project_by_name(project)
if os.environ.get('TRACKER_PROJECT_ID'):
return os.environ['TRACKER_PROJECT_ID']
elif os.environ.get('TRACKER_PROJECT_NAME'):
return os.environ['TRACKER_PROJECT_NAME']
def get_projects(self): def get_projects(self):
return gazu.project.all_open_projects() return gazu.project.all_open_projects()
def get_episode(self, episode, project=None):
project = self.get_project(project)
if episode:
episode_id = self.get_id(episode)
if episode_id:
return episode_id
return gazu.shot.get_episode_by_name(project, episode)
if os.environ.get('TRACKER_EPISODE_ID'):
return os.environ['TRACKER_EPISODE_ID']
elif os.environ.get('TRACKER_EPISODE_NAME'):
return os.environ['TRACKER_EPISODE_NAME']
def get_episodes(self, project): def get_episodes(self, project):
return gazu.shot.all_episodes_for_project(project) return gazu.shot.all_episodes_for_project(project)
def get_episode(self, name): def get_task_type(self, task_type=None):
return gazu.shot.get_episode_by_name(self.project, name) print('get_task_type', task_type)
return gazu.task.get_task_type_by_name(task_type)
def get_asset_types(self, project): def get_shot_task_types(self, project=None):
project = self.get_project(project)
task_types = gazu.task.all_task_types_for_project(project)
return [t for t in task_types if t['for_entity'].lower() == 'shot']
def get_shots_metadata(self, project=None):
project = self.get_project(project)
metadatas = []
for metadata in gazu.project.all_metadata_descriptors(project):
if metadata['entity_type'] == 'Shot' and metadata['name']:
metadatas.append(metadata)
return metadatas
def get_task_status(self, status=None):
status_id = self.get_id(status)
if status_id:
return status_id
return gazu.client.fetch_first('task-status', {"short_name": status.lower()})
def get_task_statuses(self, project=None):
project = self.get_project(project)
return gazu.task.all_task_statuses_for_project(project)
def get_asset_types(self, project=None):
project = self.get_project(project)
return gazu.asset.all_asset_types_for_project(project) return gazu.asset.all_asset_types_for_project(project)
def get_assets(self, project): def get_sequence(self, sequence, project=None):
print(f'get_sequence({sequence=}, {project=})')
project = self.get_project(project)
sequence_id = self.get_id(sequence)
if sequence_id:
return sequence_id
return gazu.shot.get_sequence_by_name(project, sequence)
def get_shot(self, shot, sequence, project=None):
print(f'get_shot({shot=}, {sequence=}, {project=})')
project = self.get_project(project)
sequence = self.get_sequence(sequence, project)
if not sequence:
return
shot_id = self.get_id(shot)
if shot_id:
return shot_id
return gazu.shot.get_shot_by_name(sequence, shot)
def get_assets(self, project=None):
project = self.get_project(project)
assets = gazu.asset.all_assets_for_project(project) assets = gazu.asset.all_assets_for_project(project)
entity_types = self.get_asset_types(project) entity_types = self.get_asset_types(project)
entity_types_ids = {e['id']: e['name'] for e in entity_types} entity_types_ids = {e['id']: e['name'] for e in entity_types}
@ -55,14 +184,54 @@ class Kitsu(Tracker):
return assets return assets
def get_shots_metadata(self, project): def get_last_comment(self, task):
metadatas = [] task = self.get_id(task)
return gazu.task.get_last_comment_for_task(task)
for metadata in gazu.project.all_metadata_descriptors(project): def get_task(self, task=None, entity=None):
if metadata['entity_type'] == 'Shot' and metadata['name']: entity = self.get_id(entity)
metadatas.append(metadata)
return metadatas task_type = self.get_task_type(task)
print('task_type', task_type)
print('task_type', task_type)
task = gazu.task.get_task_by_name(entity, task_type)
#task = gazu.task.get_task(task['id'])
task['last_comment'] = self.get_last_comment(task)
return task
def new_comment(self, task, status=None, comment='', preview=None, set_main_preview=False):
#task = self.get_task(task)
#print('Add Comment', status)
if status is None:
#print(task)
status = {'id' : task['task_status_id']}
else:
status = self.get_task_status(status)
comment = gazu.task.add_comment(
task=task,
task_status=status,
comment=comment )
if preview:
logging.info(f'Adding Preview to Kitsu {preview}')
preview = self.add_preview(
task=task,
comment=comment,
preview=str(preview) )
if set_main_preview:
gazu.task.set_main_preview(preview)
return comment
def download_preview(self, preview_id, filepath): def download_preview(self, preview_id, filepath):
if isinstance(filepath, str): if isinstance(filepath, str):
@ -74,100 +243,43 @@ class Kitsu(Tracker):
filepath.parent.mkdir(parents=True, exist_ok=True) filepath.parent.mkdir(parents=True, exist_ok=True)
gazu.files.download_preview_file_thumbnail(preview_id, filepath.as_posix()) gazu.files.download_preview_file_thumbnail(preview_id, filepath.as_posix())
def connect(self, url=None, login=None, password=None): def new_sequence(self, sequence, episode=None, project=None):
'''Connect to kitsu api using provided url, login and password''' project = self.get_project(project)
urllib3.disable_warnings() params = dict(name=sequence, project=project)
episode = self.get_episode(episode)
if episode:
params['episode'] = episode
if not self.url: return gazu.shot.new_sequence(**params)
print(f'Kitsu Url: {self.url} is empty')
return
url = self.url def new_shot(self, shot, sequence, nb_frames=None, frame_in=None,
if not url.endswith('/api'): frame_out=None, description='', custom_data=None, project=None):
url += '/api'
print(f'Info: Setting Host for kitsu {url}') project = self.get_project(project)
gazu.client.set_host(url) sequence = self.get_sequence(sequence)
if not gazu.client.host_is_up(): custom_data = custom_data or {}
print('Error: Kitsu Host is down')
try: if frame_in is not None:
print(f'Info: Log in to kitsu as {self.login}') custom_data["frame_in"] = frame_in
res = gazu.log_in(self.login, self.password) if frame_out is not None:
print(f'Info: Sucessfully login to Kitsu as {res["user"]["full_name"]}') custom_data["frame_out"] = frame_out
return res['user']
except Exception as e:
print(f'Error: {traceback.format_exc()}')
def get_asset_path(self, name, catalog, directory=None): params = dict(name=shot, data=custom_data,
directory = directory or self.source_directory sequence_id=self.get_id(sequence), description=description)
return Path(directory, self.get_asset_relative_path(name, catalog))
def get_asset_info(self, data, asset_path): if nb_frames is not None:
modified = time.time_ns() params["nb_frames"] = nb_frames
catalog = data['entity_type_name'].title()
asset_path = self.prop_rel_path(asset_path, 'source_directory')
asset_info = dict( shot = self.get_shot(shot=shot, sequence=sequence)
filepath=asset_path, if shot:
modified=modified, return shot
library_id=self.library.id, else:
assets=[dict( path = f"data/projects/{self.get_id(project)}/shots"
catalog=catalog, return gazu.client.post(path, params)
metadata=data.get('data', {}),
description=data['description'],
tags=[],
type=self.data_type,
name=data['name'])
]
)
return asset_info def draw_prefs(self, layout):
layout.prop(self, 'url', text='Url')
def fetch(self): layout.prop(self, 'login', text='Login')
"""Gather in a list all assets found in the folder""" layout.prop(self, 'password', text='Password')
print(f'Fetch Assets for {self.library.name}')
self.connect()
template_file = Template(self.template_file)
template_name = Template(self.template_name)
project = gazu.client.fetch_first('projects', {'name': self.project_name})
entity_types = gazu.client.fetch_all('entity-types')
entity_types_ids = {e['id']: e['name'] for e in entity_types}
cache = self.read_cache()
for asset_data in gazu.asset.all_assets_for_project(project):
asset_data['entity_type_name'] = entity_types_ids[asset_data.pop('entity_type_id')]
asset_name = asset_data['name']
asset_field_data = dict(asset_name=asset_name, type=asset_data['entity_type_name'], source_directory=self.source_directory)
try:
asset_field_data.update(template_name.parse(asset_name))
except Exception:
print(f'Warning: Could not parse {asset_name} with template {template_name}')
asset_path = template_file.find(asset_field_data)
if not asset_path:
print(f'Warning: Could not find file for {template_file.format(asset_field_data)}')
continue
asset_path = self.prop_rel_path(asset_path, 'source_directory')
asset_cache_data = dict(
catalog=asset_data['entity_type_name'].title(),
metadata=asset_data.get('data', {}),
description=asset_data['description'],
tags=[],
type=self.data_type,
name=asset_data['name']
)
cache.add_asset_cache(asset_cache_data, filepath=asset_path)
return cache

View File

@ -1,4 +1,9 @@
import bpy
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import PointerProperty, StringProperty
from vse_toolbox.file_utils import norm_str
class Tracker(PropertyGroup): class Tracker(PropertyGroup):
pass pass

View File

@ -1,10 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
import bpy
import re import re
from bpy.app.handlers import persistent
from pathlib import Path from pathlib import Path
import bpy
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
@ -122,9 +123,31 @@ def set_channels():
for i, c in enumerate(items.keys(), start=1): for i, c in enumerate(items.keys(), start=1):
scn.sequence_editor.channels[i].name = c.title() scn.sequence_editor.channels[i].name = c.title()
def render_strips(strips): def get_strip_render_path(strip, template):
return template.format(strip_name=strip.name, ext=Path(scn.render.frame_path()).suffix)
def render_strips(strips, template):
scn = bpy.context.scene
scene_start = scn.frame_start
scene_end = scn.frame_end
for strip in strips: for strip in strips:
print(strip.name) #print(render_template, strip.name, path)
scn.frame_start = strip.frame_final_start
scn.frame_end = strip.frame_final_end - 1
## render animatic
#strip_render_path = render_template.format(strip_name=strip.name, ext=Path(scn.render.frame_path()).suffix)
scn.render.filepath = get_strip_render_path(strip, template)
#print(scn.render.filepath)
print(f'Render Strip to {scn.render.filepath}')
#bpy.ops.render.render(animation=True)
bpy.ops.render.opengl(animation=True, sequencer=True)
scn.frame_start = scene_start
scn.frame_end = scene_end
def import_edit(filepath, adapter="cmx_3600", clean_sequencer=False): def import_edit(filepath, adapter="cmx_3600", clean_sequencer=False):
import opentimelineio as otio import opentimelineio as otio