Merge branch 'release/2.13.4' into 2.x/develop

This commit is contained in:
Milan Kolar 2020-11-09 21:48:11 +01:00
commit bcd57e5177
25 changed files with 238 additions and 76 deletions

View file

@ -978,24 +978,30 @@ class WorkfileSettings(object):
self.set_colorspace()
def set_favorites(self):
anatomy = get_anatomy()
work_template = anatomy.templates["work"]["path"]
projects_root = anatomy.root_value_for_template(work_template)
work_dir = os.getenv("AVALON_WORKDIR")
asset = os.getenv("AVALON_ASSET")
project = os.getenv("AVALON_PROJECT")
hierarchy = os.getenv("AVALON_HIERARCHY")
favorite_items = OrderedDict()
# project
favorite_items.update({"Project dir": os.path.join(
projects_root, project).replace("\\", "/")})
# shot
favorite_items.update({"Shot dir": os.path.join(
projects_root, project,
hierarchy, asset).replace("\\", "/")})
# get project's root and split to parts
projects_root = os.path.normpath(work_dir.split(
project)[0])
# add project name
project_dir = os.path.join(projects_root, project) + "/"
# add to favorites
favorite_items.update({"Project dir": project_dir.replace("\\", "/")})
# asset
asset_root = os.path.normpath(work_dir.split(
asset)[0])
# add asset name
asset_dir = os.path.join(asset_root, asset) + "/"
# add to favorites
favorite_items.update({"Shot dir": asset_dir.replace("\\", "/")})
# workdir
favorite_items.update({"Work dir": work_dir})
favorite_items.update({"Work dir": work_dir.replace("\\", "/")})
set_context_favorites(favorite_items)
@ -1388,8 +1394,18 @@ class ExporterReviewMov(ExporterReview):
self.log.debug("Path: {}".format(self.path))
write_node["file"].setValue(self.path)
write_node["file_type"].setValue(self.ext)
write_node["meta_codec"].setValue("ap4h")
write_node["mov64_codec"].setValue("ap4h")
# Knobs `meta_codec` and `mov64_codec` are not available on centos.
# TODO change this to use conditions, if possible.
try:
write_node["meta_codec"].setValue("ap4h")
except Exception:
self.log.info("`meta_codec` knob was not found")
try:
write_node["mov64_codec"].setValue("ap4h")
except Exception:
self.log.info("`mov64_codec` knob was not found")
write_node["mov64_write_timecode"].setValue(1)
write_node["raw"].setValue(1)
# connect

View file

@ -1420,13 +1420,15 @@ class BuildWorkfile:
return output
def ffprobe_streams(path_to_file):
def ffprobe_streams(path_to_file, logger=None):
"""Load streams from entered filepath via ffprobe."""
log.info(
if not logger:
logger = log
logger.info(
"Getting information about input \"{}\".".format(path_to_file)
)
args = [
get_ffmpeg_tool_path("ffprobe"),
"\"{}\"".format(get_ffmpeg_tool_path("ffprobe")),
"-v quiet",
"-print_format json",
"-show_format",
@ -1434,12 +1436,21 @@ def ffprobe_streams(path_to_file):
"\"{}\"".format(path_to_file)
]
command = " ".join(args)
log.debug("FFprobe command: \"{}\"".format(command))
popen = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
logger.debug("FFprobe command: \"{}\"".format(command))
popen = subprocess.Popen(
command,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
popen_output = popen.communicate()[0]
log.debug("FFprobe output: {}".format(popen_output))
return json.loads(popen_output)["streams"]
popen_stdout, popen_stderr = popen.communicate()
if popen_stdout:
logger.debug("ffprobe stdout: {}".format(popen_stdout))
if popen_stderr:
logger.debug("ffprobe stderr: {}".format(popen_stderr))
return json.loads(popen_stdout)["streams"]
def source_hash(filepath, *args):

View file

@ -974,7 +974,7 @@ class ExtractBurnin(pype.api.Extractor):
args = [executable, scriptpath, json_data]
self.log.debug("Executing: {}".format(args))
output = pype.api.subprocess(args, shell=True)
output = pype.api.subprocess(args, shell=True, logger=self.log)
self.log.debug("Output: {}".format(output))
repre_update = {

View file

@ -72,7 +72,7 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin):
ffmpeg_args = self.ffmpeg_args or {}
jpeg_items = []
jpeg_items.append(ffmpeg_path)
jpeg_items.append("\"{}\"".format(ffmpeg_path))
# override file if already exists
jpeg_items.append("-y")
# use same input args like with mov

View file

@ -465,7 +465,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
audio_filters.append(arg)
all_args = []
all_args.append(self.ffmpeg_path)
all_args.append("\"{}\"".format(self.ffmpeg_path))
all_args.extend(input_args)
if video_filters:
all_args.append("-filter:v {}".format(",".join(video_filters)))
@ -649,7 +649,9 @@ class ExtractReview(pyblish.api.InstancePlugin):
# NOTE Skipped using instance's resolution
full_input_path_single_file = temp_data["full_input_path_single_file"]
input_data = pype.lib.ffprobe_streams(full_input_path_single_file)[0]
input_data = pype.lib.ffprobe_streams(
full_input_path_single_file, self.log
)[0]
input_width = int(input_data["width"])
input_height = int(input_data["height"])
@ -1542,7 +1544,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
os.mkdir(stg_dir)
mov_args = [
ffmpeg_path,
"\"{}\"".format(ffmpeg_path),
" ".join(input_args),
" ".join(output_args)
]

View file

@ -26,7 +26,7 @@ class ExtractReviewSlate(pype.api.Extractor):
slate_path = inst_data.get("slateFrame")
ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg")
slate_stream = pype.lib.ffprobe_streams(slate_path)[0]
slate_stream = pype.lib.ffprobe_streams(slate_path, self.log)[0]
slate_width = slate_stream["width"]
slate_height = slate_stream["height"]
@ -178,7 +178,7 @@ class ExtractReviewSlate(pype.api.Extractor):
_remove_at_end.append(slate_v_path)
slate_args = [
ffmpeg_path,
"\"{}\"".format(ffmpeg_path),
" ".join(input_args),
" ".join(output_args)
]
@ -299,7 +299,7 @@ class ExtractReviewSlate(pype.api.Extractor):
try:
# Get information about input file via ffprobe tool
streams = pype.lib.ffprobe_streams(full_input_path)
streams = pype.lib.ffprobe_streams(full_input_path, self.log)
except Exception:
self.log.warning(
"Could not get codec data from input.",

View file

@ -615,12 +615,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
# copy file with speedcopy and check if size of files are simetrical
while True:
import shutil
try:
if not shutil._samefile(src, dst):
copyfile(src, dst)
except shutil.SameFileError:
self.log.critical("files are the same {} to {}".format(src,
dst))
else:
self.log.critical(
"files are the same {} to {}".format(src, dst)
)
os.remove(dst)
try:
shutil.copyfile(src, dst)

View file

@ -1024,6 +1024,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
version = pype.api.get_latest_version(asset, subset)
if version:
version = int(version["name"]) + 1
else:
version = 1
template_data["subset"] = subset
template_data["family"] = "render"

View file

@ -29,6 +29,6 @@ class ValidateFFmpegInstalled(pyblish.api.ContextPlugin):
def process(self, context):
ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg")
self.log.info("ffmpeg path: `{}`".format(ffmpeg_path))
if self.is_tool(ffmpeg_path) is False:
if self.is_tool("\"{}\"".format(ffmpeg_path)) is False:
self.log.error("ffmpeg not found in PATH")
raise RuntimeError('ffmpeg not installed.')

View file

@ -90,7 +90,7 @@ class ExtractRender(pyblish.api.InstancePlugin):
thumbnail_path = os.path.join(path, "thumbnail.png")
ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg")
args = [
ffmpeg_path, "-y",
"\"{}\"".format(ffmpeg_path), "-y",
"-i", os.path.join(path, list(collections[0])[0]),
"-vf", "scale=300:-1",
"-vframes", "1",

View file

@ -130,8 +130,8 @@ class ExtractReviewCutUp(pype.api.Extractor):
# check if audio stream is in input video file
ffprob_cmd = (
"{ffprobe_path} -i \"{full_input_path}\" -show_streams "
"-select_streams a -loglevel error"
"\"{ffprobe_path}\" -i \"{full_input_path}\" -show_streams"
" -select_streams a -loglevel error"
).format(**locals())
self.log.debug("ffprob_cmd: {}".format(ffprob_cmd))
@ -171,7 +171,8 @@ class ExtractReviewCutUp(pype.api.Extractor):
# try to get video native resolution data
try:
resolution_output = pype.api.subprocess((
"{ffprobe_path} -i \"{full_input_path}\" -v error "
"\"{ffprobe_path}\" -i \"{full_input_path}\""
" -v error "
"-select_streams v:0 -show_entries "
"stream=width,height -of csv=s=x:p=0"
).format(**locals()))
@ -274,7 +275,7 @@ class ExtractReviewCutUp(pype.api.Extractor):
output_args.append("-y \"{}\"".format(full_output_path))
mov_args = [
ffmpeg_path,
"\"{}\"".format(ffmpeg_path),
" ".join(input_args),
" ".join(output_args)
]

View file

@ -34,8 +34,6 @@ class ExtractImage(pype.api.Extractor):
# limit unnecessary calls to client
if layer.visible and layer.id not in extract_ids:
stub.set_visible(layer.id, False)
if not layer.visible and layer.id in extract_ids:
stub.set_visible(layer.id, True)
save_options = []
if "png" in self.formats:

View file

@ -38,8 +38,6 @@ class ExtractReview(pype.api.Extractor):
# limit unnecessary calls to client
if layer.visible and layer.id not in extract_ids:
stub.set_visible(layer.id, False)
if not layer.visible and layer.id in extract_ids:
stub.set_visible(layer.id, True)
stub.saveAs(output_image_path, 'jpg', True)
@ -56,7 +54,7 @@ class ExtractReview(pype.api.Extractor):
# Generate thumbnail.
thumbnail_path = os.path.join(staging_dir, "thumbnail.jpg")
args = [
ffmpeg_path, "-y",
"\"{}\"".format(ffmpeg_path), "-y",
"-i", output_image_path,
"-vf", "scale=300:-1",
"-vframes", "1",

View file

@ -47,7 +47,7 @@ class ExtractShotData(pype.api.Extractor):
start += 0.5
args = [
ffmpeg_path,
"\"{}\"".format(ffmpeg_path),
"-ss", str(start / fps),
"-i", f"\"{video_file_path}\"",
"-t", str(dur / fps)

View file

@ -75,7 +75,7 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin):
ffmpeg_args = self.ffmpeg_args or {}
jpeg_items = []
jpeg_items.append(ffmpeg_path)
jpeg_items.append("\"{}\"".format(ffmpeg_path))
# override file if already exists
jpeg_items.append("-y")
# add input filters from peresets

View file

@ -10,12 +10,28 @@ def get_resource(*args):
"""
return os.path.normpath(
os.path.join(
os.path.dirname(__file__),
os.path.dirname(os.path.abspath(__file__)),
*args
)
)
def get_liberation_font_path(bold=False, italic=False):
font_name = "LiberationSans"
suffix = ""
if bold:
suffix += "Bold"
if italic:
suffix += "Italic"
if not suffix:
suffix = "Regular"
filename = "{}-{}.ttf".format(font_name, suffix)
font_path = get_resource("fonts", font_name, filename)
return font_path
def pype_icon_filepath(debug=None):
if debug is None:
debug = bool(os.getenv("PYPE_DEV"))

View file

@ -0,0 +1,77 @@
LICENSE AGREEMENT AND LIMITED PRODUCT WARRANTY LIBERATION FONT SOFTWARE
This agreement governs the use of the Software and any updates to the
Software, regardless of the delivery mechanism. Subject to the following
terms, Red Hat, Inc. ("Red Hat") grants to the user ("Client") a license to
this collective work pursuant to the GNU General Public License v.2 with the
exceptions set forth below and such other terms as our set forth in this End
User License Agreement.
1. The Software and License Exception. LIBERATION font software (the
"Software") consists of TrueType-OpenType formatted font software for
rendering LIBERATION typefaces in sans serif, serif, and monospaced character
styles. You are licensed to use, modify, copy, and distribute the Software
pursuant to the GNU General Public License v.2 with the following exceptions:
1) As a special exception, if you create a document which uses this font, and
embed this font or unaltered portions of this font into the document, this
font does not by itself cause the resulting document to be covered by the GNU
General Public License.  This exception does not however invalidate any other
reasons why the document might be covered by the GNU General Public License. 
If you modify this font, you may extend this exception to your version of the
font, but you are not obligated to do so. If you do not wish to do so, delete
this exception statement from your version.
2) As a further exception, any distribution of the object code of the Software
in a physical product must provide you the right to access and modify the
source code for the Software and to reinstall that modified version of the
Software in object code form on the same physical product on which you
received it.
2. Intellectual Property Rights. The Software and each of its components,
including the source code, documentation, appearance, structure and
organization are owned by Red Hat and others and are protected under copyright
and other laws. Title to the Software and any component, or to any copy,
modification, or merged portion shall remain with the aforementioned, subject
to the applicable license. The "LIBERATION" trademark is a trademark of Red
Hat, Inc. in the U.S. and other countries. This agreement does not permit
Client to distribute modified versions of the Software using Red Hat's
trademarks. If Client makes a redistribution of a modified version of the
Software, then Client must modify the files names to remove any reference to
the Red Hat trademarks and must not use the Red Hat trademarks in any way to
reference or promote the modified Software.
3. Limited Warranty. To the maximum extent permitted under applicable law, the
Software is provided and licensed "as is" without warranty of any kind,
expressed or implied, including the implied warranties of merchantability,
non-infringement or fitness for a particular purpose. Red Hat does not warrant
that the functions contained in the Software will meet Client's requirements
or that the operation of the Software will be entirely error free or appear
precisely as described in the accompanying documentation.
4. Limitation of Remedies and Liability. To the maximum extent permitted by
applicable law, Red Hat or any Red Hat authorized dealer will not be liable to
Client for any incidental or consequential damages, including lost profits or
lost savings arising out of the use or inability to use the Software, even if
Red Hat or such dealer has been advised of the possibility of such damages.
5. Export Control. As required by U.S. law, Client represents and warrants
that it: (a) understands that the Software is subject to export controls under
the U.S. Commerce Department's Export Administration Regulations ("EAR"); (b)
is not located in a prohibited destination country under the EAR or U.S.
sanctions regulations (currently Cuba, Iran, Iraq, Libya, North Korea, Sudan
and Syria); (c) will not export, re-export, or transfer the Software to any
prohibited destination, entity, or individual without the necessary export
license(s) or authorizations(s) from the U.S. Government; (d) will not use or
transfer the Software for use in any sensitive nuclear, chemical or biological
weapons, or missile technology end-uses unless authorized by the U.S.
Government by regulation or specific license; (e) understands and agrees that
if it is in the United States and exports or transfers the Software to
eligible end users, it will, as required by EAR Section 740.17(e), submit
semi-annual reports to the Commerce Department's Bureau of Industry & Security
(BIS), which include the name and address (including country) of each
transferee; and (f) understands that countries other than the United States
may restrict the import, use, or export of encryption products and that it
shall be solely responsible for compliance with any such import, use, or
export restrictions.
6. General. If any provision of this agreement is held to be unenforceable,
that shall not affect the enforceability of the remaining provisions. This
agreement shall be governed by the laws of the State of North Carolina and of
the United States, without regard to any conflict of laws provisions, except
that the United Nations Convention on the International Sale of Goods shall
not apply.
Copyright © 2007 Red Hat, Inc. All rights reserved. LIBERATION is a trademark
of Red Hat, Inc.

View file

@ -2,9 +2,10 @@ import os
import sys
import re
import subprocess
import platform
import json
import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins
from pype.api import config
from pype.api import config, resources
import pype.lib
@ -13,16 +14,16 @@ ffprobe_path = pype.lib.get_ffmpeg_tool_path("ffprobe")
FFMPEG = (
'{} -i "%(input)s" %(filters)s %(args)s%(output)s'
'"{}" -i "%(input)s" %(filters)s %(args)s%(output)s'
).format(ffmpeg_path)
FFPROBE = (
'{} -v quiet -print_format json -show_format -show_streams "%(source)s"'
'"{}" -v quiet -print_format json -show_format -show_streams "%(source)s"'
).format(ffprobe_path)
DRAWTEXT = (
"drawtext=text=\\'%(text)s\\':x=%(x)s:y=%(y)s:fontcolor="
"%(color)s@%(opacity).1f:fontsize=%(size)d:fontfile='%(font)s'"
"drawtext=fontfile='%(font)s':text=\\'%(text)s\\':"
"x=%(x)s:y=%(y)s:fontcolor=%(color)s@%(opacity).1f:fontsize=%(size)d"
)
TIMECODE = (
"drawtext=timecode=\\'%(timecode)s\\':text=\\'%(text)s\\'"
@ -236,13 +237,32 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins):
}
timecode_text = options.get("timecode") or ""
text_for_size += timecode_text
data.update(options)
os_system = platform.system().lower()
data_font = data.get("font")
if not data_font:
data_font = (
resources.get_liberation_font_path().replace("\\", "/")
)
elif isinstance(data_font, dict):
data_font = data_font[os_system]
if data_font:
data["font"] = data_font
options["font"] = data_font
if ffmpeg_burnins._is_windows():
data["font"] = (
data_font
.replace(os.sep, r'\\' + os.sep)
.replace(':', r'\:')
)
data.update(
ffmpeg_burnins._drawtext(align, resolution, text_for_size, options)
)
if 'font' in data and ffmpeg_burnins._is_windows():
data['font'] = data['font'].replace(os.sep, r'\\' + os.sep)
data['font'] = data['font'].replace(':', r'\:')
self.filters['drawtext'].append(draw % data)
if options.get('bg_color') is not None:
@ -474,7 +494,7 @@ def burnins_from_data(
# Replace with missing key value if frame_start_tc is not set
if frame_start_tc is None and has_timecode:
has_timecode = False
log.warning(
print(
"`frame_start` and `frame_start_tc`"
" are not set in entered data."
)
@ -483,7 +503,7 @@ def burnins_from_data(
has_source_timecode = SOURCE_TIMECODE_KEY in value
if source_timecode is None and has_source_timecode:
has_source_timecode = False
log.warning("Source does not have set timecode value.")
print("Source does not have set timecode value.")
value = value.replace(SOURCE_TIMECODE_KEY, MISSING_KEY_VALUE)
key_pattern = re.compile(r"(\{.*?[^{0]*\})")

View file

@ -9,6 +9,7 @@ import os
import sys
import traceback
import inspect
import logging
from Qt import QtCore
@ -29,6 +30,7 @@ class IterationBreak(Exception):
class Controller(QtCore.QObject):
log = logging.getLogger("PyblishController")
# Emitted when the GUI is about to start processing;
# e.g. resetting, validating or publishing.
about_to_process = QtCore.Signal(object, object)
@ -72,6 +74,8 @@ class Controller(QtCore.QObject):
self.instance_toggled.connect(self._on_instance_toggled)
def reset_variables(self):
self.log.debug("Resetting pyblish context variables")
# Data internal to the GUI itself
self.is_running = False
self.stopped = False
@ -113,6 +117,7 @@ class Controller(QtCore.QObject):
"nextOrder": None,
"ordersWithError": set()
}
self.log.debug("Reset of pyblish context variables done")
def presets_by_hosts(self):
# Get global filters as base
@ -138,6 +143,8 @@ class Controller(QtCore.QObject):
return result
def reset_context(self):
self.log.debug("Resetting pyblish context object")
self.context = pyblish.api.Context()
self.context._publish_states = InstanceStates.ContextType
@ -159,6 +166,8 @@ class Controller(QtCore.QObject):
self.context.families = ("__context__",)
self.log.debug("Reset of pyblish context object done")
def reset(self):
"""Discover plug-ins and run collection."""
@ -202,6 +211,7 @@ class Controller(QtCore.QObject):
self.was_finished.emit()
def stop(self):
self.log.debug("Stopping")
self.stopped = True
def act(self, plugin, action):
@ -346,27 +356,30 @@ class Controller(QtCore.QObject):
This process don't stop on one
"""
def on_next():
self.log.debug("Looking for next pair to process")
try:
self.current_pair = next(self.pair_generator)
if isinstance(self.current_pair, IterationBreak):
raise self.current_pair
except IterationBreak:
self.log.debug("Iteration break was raised")
self.is_running = False
self.was_stopped.emit()
return
except StopIteration:
self.log.debug("Iteration stop was raised")
self.is_running = False
# All pairs were processed successfully!
return util.defer(500, on_finished)
except Exception:
# This is a bug
exc_type, exc_msg, exc_tb = sys.exc_info()
traceback.print_exception(exc_type, exc_msg, exc_tb)
self.is_running = False
self.was_stopped.emit()
except Exception as exc:
self.log.warning(
"Unexpected exception during `on_next` happened",
exc_info=True
)
exc_msg = str(exc)
return util.defer(
500, lambda: on_unexpected_error(error=exc_msg)
)
@ -376,19 +389,23 @@ class Controller(QtCore.QObject):
def on_process():
try:
self.log.debug(
"Processing pair: {}".format(str(self.current_pair))
)
result = self._process(*self.current_pair)
if result["error"] is not None:
self.log.debug("Error happened")
self.errored = True
self.log.debug("Pair processed")
self.was_processed.emit(result)
except Exception:
# TODO this should be handled much differently
# TODO emit crash signal to show message box with traceback
exc_type, exc_msg, exc_tb = sys.exc_info()
traceback.print_exception(exc_type, exc_msg, exc_tb)
self.is_running = False
self.was_stopped.emit()
except Exception as exc:
self.log.warning(
"Unexpected exception during `on_process` happened",
exc_info=True
)
exc_msg = str(exc)
return util.defer(
500, lambda: on_unexpected_error(error=exc_msg)
)
@ -396,6 +413,10 @@ class Controller(QtCore.QObject):
util.defer(10, on_next)
def on_unexpected_error(error):
# TODO this should be handled much differently
# TODO emit crash signal to show message box with traceback?
self.is_running = False
self.was_stopped.emit()
util.u_print(u"An unexpected error occurred:\n %s" % error)
return util.defer(500, on_finished)
@ -446,9 +467,9 @@ class Controller(QtCore.QObject):
try:
callback(instance, old_value, new_value)
except Exception:
print(
self.log.warning(
"Callback for `instanceToggled` crashed. {}".format(
os.path.abspath(inspect.getfile(callback))
)
),
exc_info=True
)
traceback.print_exception(*sys.exc_info())

View file

@ -266,7 +266,7 @@ class DropDataFrame(QtWidgets.QFrame):
def load_data_with_probe(self, filepath):
ffprobe_path = pype.lib.get_ffmpeg_tool_path("ffprobe")
args = [
ffprobe_path,
"\"{}\"".format(ffprobe_path),
'-v', 'quiet',
'-print_format json',
'-show_format',

View file

@ -1 +1 @@
__version__ = "2.13.0"
__version__ = "2.13.4"