Merged in bugfix/PYPE-160-mov-generation (pull request #60)

Bugfix/PYPE-160 mov generation

Approved-by: Milan Kolar <milan@orbi.tools>
This commit is contained in:
Ondřej Samohel 2019-02-09 23:13:43 +00:00 committed by Milan Kolar
commit d800519176
7 changed files with 99 additions and 114 deletions

View file

@ -0,0 +1,28 @@
import pyblish.api
import os
import subprocess
class ValidateFfmpegInstallef(pyblish.api.Validator):
"""Validate availability of ffmpeg tool in PATH"""
order = pyblish.api.ValidatorOrder
label = 'Validate ffmpeg installation'
families = ['review']
optional = True
def is_tool(self, name):
try:
devnull = open(os.devnull, "w")
subprocess.Popen(
[name], stdout=devnull, stderr=devnull
).communicate()
except OSError as e:
if e.errno == os.errno.ENOENT:
return False
return True
def process(self, instance):
if self.is_tool('ffmpeg') is False:
self.log.error("ffmpeg not found in PATH")
raise RuntimeError('ffmpeg not installed.')

View file

@ -26,4 +26,7 @@ class CollectModelData(pyblish.api.InstancePlugin):
instance.data['endFrame'] = frame
# make ftrack publishable
instance.data["families"] = ['ftrack']
if instance.data.get('families'):
instance.data['families'].append('ftrack')
else:
instance.data['families'] = ['ftrack']

View file

@ -4,7 +4,7 @@ import pymel.core as pm
import pyblish.api
import avalon.api
class CollectReviewData(pyblish.api.InstancePlugin):
class CollectReview(pyblish.api.InstancePlugin):
"""Collect Review data
"""
@ -15,12 +15,9 @@ class CollectReviewData(pyblish.api.InstancePlugin):
def process(self, instance):
# make ftrack publishable
instance.data["families"] = ['ftrack']
context = instance.context
self.log.debug('instance: {}'.format(instance))
task = avalon.api.Session["AVALON_TASK"]
# pseudo code
# get cameras
members = instance.data['setMembers']
@ -33,7 +30,7 @@ class CollectReviewData(pyblish.api.InstancePlugin):
camera = cameras[0]
self.log.debug('camera: {}'.format(camera))
objectset = context.data['objectsets']
objectset = instance.context.data['objectsets']
reviewable_subset = None
reviewable_subset = list(set(members) & set(objectset))
@ -41,18 +38,34 @@ class CollectReviewData(pyblish.api.InstancePlugin):
assert len(reviewable_subset) <= 1, "Multiple subsets for review"
self.log.debug('subset for review: {}'.format(reviewable_subset))
for inst in context:
self.log.debug('instance: {}'.format(instance))
i = 0
for inst in instance.context:
data = instance.context[i].data
if inst.name == reviewable_subset[0]:
if inst.data.get('families'):
inst.data['families'].append('review')
if data.get('families'):
data['families'].append('review')
else:
inst.data['families'] = ['review']
inst.data['review_camera'] = camera
self.log.info('adding review family to {}'.format(reviewable_subset))
data['families'] = ['review']
self.log.debug('adding review family to {}'.format(reviewable_subset))
data['review_camera'] = camera
data["publish"] = False
data['startFrameReview'] = instance.data['startFrame']
data['endFrameReview'] = instance.data['endFrame']
data['handles'] = instance.data['handles']
data['step'] = instance.data['step']
data['fps'] = instance.data['fps']
cmds.setAttr(str(instance) + '.active', 0)
inst.data['publish'] = 0
inst.data['active'] = 0
i += 1
instance.context[i].data.update(data)
instance.data['remove'] = True
else:
instance.data['subset'] = task + 'Review'
instance.data['review_camera'] = camera
instance.data['startFrameReview'] = instance.data['startFrame']
instance.data['endFrameReview'] = instance.data['endFrame']
# make ftrack publishable
instance.data["families"] = ['ftrack']
cmds.setAttr(str(instance) + '.active', 1)

View file

@ -1,38 +1,26 @@
import os
import subprocess
import contextlib
import time
import sys
import capture_gui
import clique
import pype.maya.lib as lib
import pype.api
import avalon.maya
from maya import cmds
from maya import cmds, mel
import pymel.core as pm
from pype.vendor import ffmpeg
reload(ffmpeg)
import avalon.maya
# import maya_utils as mu
# from tweakHUD import master
# from tweakHUD import draft_hud as dHUD
# from tweakHUD import ftrackStrings as fStrings
#
# def soundOffsetFunc(oSF, SF, H):
# tmOff = (oSF - H) - SF
# return tmOff
# TODO: move codec settings to presets
class ExtractQuicktime(pype.api.Extractor):
"""Extract a Camera as Alembic.
"""Extract Quicktime from viewport capture.
The cameras gets baked to world space by default. Only when the instance's
`bakeToWorldSpace` is set to False it will include its full hierarchy.
Takes review camera and creates review Quicktime video based on viewport
capture.
"""
@ -44,8 +32,17 @@ class ExtractQuicktime(pype.api.Extractor):
def process(self, instance):
self.log.info("Extracting capture..")
start = instance.data.get("startFrame", 1)
end = instance.data.get("endFrame", 25)
# get scene fps
fps = mel.eval('currentTimeUnitToFPS()')
# if start and end frames cannot be determined, get them
# from Maya timeline
start = instance.data.get("startFrameReview")
end = instance.data.get("endFrameReview")
if start is None:
start = cmds.playbackOptions(query=True, animationStartTime=True)
if end is None:
end = cmds.playbackOptions(query=True, animationEndTime=True)
self.log.info("start: {}, end: {}".format(start, end))
handles = instance.data.get("handles", 0)
if handles:
@ -53,46 +50,7 @@ class ExtractQuicktime(pype.api.Extractor):
end += handles
# get cameras
members = instance.data['setMembers']
camera = instance.data['review_camera']
# cameras = cmds.ls(members, leaf=True, shapes=True, long=True,
# dag=True, type="camera")
# # validate required settings
# assert len(cameras) == 1, "Not a single camera found in extraction"
# camera = cameras[0]
# project_code = ftrack_data['Project']['code']
# task_type = ftrack_data['Task']['type']
#
# # load Preset
# studio_repos = os.path.abspath(os.environ.get('studio_repos'))
# shot_preset_path = os.path.join(studio_repos, 'maya',
# 'capture_gui_presets',
# (project_code + '_' + task_type + '_' + asset + '.json'))
#
# task_preset_path = os.path.join(studio_repos, 'maya',
# 'capture_gui_presets',
# (project_code + '_' + task_type + '.json'))
#
# project_preset_path = os.path.join(studio_repos, 'maya',
# 'capture_gui_presets',
# (project_code + '.json'))
#
# default_preset_path = os.path.join(studio_repos, 'maya',
# 'capture_gui_presets',
# 'default.json')
#
# if os.path.isfile(shot_preset_path):
# preset_to_use = shot_preset_path
# elif os.path.isfile(task_preset_path):
# preset_to_use = task_preset_path
# elif os.path.isfile(project_preset_path):
# preset_to_use = project_preset_path
# else:
# preset_to_use = default_preset_path
capture_preset = ""
try:
preset = lib.load_capture_preset(capture_preset)
@ -100,15 +58,13 @@ class ExtractQuicktime(pype.api.Extractor):
preset = {}
self.log.info('using viewport preset: {}'.format(capture_preset))
#preset["off_screen"] = False
preset['camera'] = camera
preset['format'] = "image"
# preset['compression'] = "qt"
preset['quality'] = 50
preset['compression'] = "jpg"
preset['start_frame'] = 1
preset['end_frame'] = 25
preset['start_frame'] = start
preset['end_frame'] = end
preset['camera_options'] = {
"displayGateMask": False,
"displayResolution": False,
@ -143,50 +99,34 @@ class ExtractQuicktime(pype.api.Extractor):
self.log.info("file list {}".format(playblast))
# self.log.info("Calculating HUD data overlay")
# stagingdir = "C:/Users/milan.kolar/AppData/Local/Temp/pyblish_tmp_ucsymm"
collected_frames = os.listdir(stagingdir)
collections, remainder = clique.assemble(collected_frames)
input_path = os.path.join(stagingdir, collections[0].format('{head}{padding}{tail}'))
input_path = os.path.join(
stagingdir, collections[0].format('{head}{padding}{tail}'))
self.log.info("input {}".format(input_path))
movieFile = filename + ".mov"
full_movie_path = os.path.join(stagingdir, movieFile)
self.log.info("output {}".format(full_movie_path))
# fls = [os.path.join(stagingdir, filename).replace("\\","/") for f in os.listdir( dir_path ) if f.endswith(preset['compression'])]
# self.log.info("file list {}}".format(fls[0]))
out, err = (
ffmpeg
.input(input_path, framerate=25)
.output(full_movie_path)
.run(overwrite_output=True)
)
with avalon.maya.suspended_refresh():
try:
(
ffmpeg
.input(input_path, framerate=fps, start_number=int(start))
.output(full_movie_path)
.run(overwrite_output=True,
capture_stdout=True,
capture_stderr=True)
)
except ffmpeg.Error as e:
ffmpeg_error = 'ffmpeg error: {}'.format(e.stderr)
self.log.error(ffmpeg_error)
raise RuntimeError(ffmpeg_error)
if "files" not in instance.data:
instance.data["files"] = list()
instance.data["files"].append(movieFile)
# ftrackStrings = fStrings.annotationData()
# nData = ftrackStrings.niceData
# nData['version'] = instance.context.data('version')
# fFrame = int(pm.playbackOptions( q = True, minTime = True))
# eFrame = int(pm.playbackOptions( q = True, maxTime = True))
# nData['frame'] = [(str("{0:05d}".format(f))) for f in range(fFrame, eFrame + 1)]
# soundOfst = int(float(nData['oFStart'])) - int(float(nData['handle'])) - fFrame
# soundFile = mu.giveMePublishedAudio()
# self.log.info("SOUND offset %s" % str(soundOfst))
# self.log.info("SOUND source video to %s" % str(soundFile))
# ann = dHUD.draftAnnotate()
# if soundFile:
# ann.addAnotation(seqFls = fls, outputMoviePth = movieFullPth, annotateDataArr = nData, soundFile = soundFile, soundOffset = soundOfst)
# else:
# ann.addAnotation(seqFls = fls, outputMoviePth = movieFullPth, annotateDataArr = nData)
# for f in fls:
# os.remove(f)
# playblast = (ann.expPth).replace("\\","/")
@contextlib.contextmanager
def maintained_time():

View file

@ -23,6 +23,7 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin):
version = (0, 1, 0)
label = 'Mesh Edge Length Non Zero'
actions = [pype.maya.action.SelectInvalidAction]
optional = True
__tolerance = 1e-5

View file

@ -9,7 +9,7 @@ def get_file_rule(rule):
return mel.eval('workspace -query -fileRuleEntry "{}"'.format(rule))
class ValidateRenderImageRule(pyblish.api.ContextPlugin):
class ValidateRenderImageRule(pyblish.api.InstancePlugin):
"""Validates "images" file rule is set to "renders/"
"""
@ -19,7 +19,7 @@ class ValidateRenderImageRule(pyblish.api.ContextPlugin):
hosts = ["maya"]
families = ["renderlayer"]
def process(self, context):
def process(self, instance):
assert get_file_rule("images") == "renders", (
"Workspace's `images` file rule must be set to: renders"