From 3a7ce1e1b2497b40dde65c70cf06ede0f8a2bafc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Dec 2018 14:14:58 +0100 Subject: [PATCH] preparation scripts added from past projects --- .../global/_publish_unused/transcode.py | 153 ++++++++++++++++++ .../extract_nuke_baked_colorspace.py | 107 ++++++++++++ 2 files changed, 260 insertions(+) create mode 100644 pype/plugins/global/_publish_unused/transcode.py create mode 100644 pype/plugins/nuke/_publish_unused/extract_nuke_baked_colorspace.py diff --git a/pype/plugins/global/_publish_unused/transcode.py b/pype/plugins/global/_publish_unused/transcode.py new file mode 100644 index 0000000000..6da65e3cc7 --- /dev/null +++ b/pype/plugins/global/_publish_unused/transcode.py @@ -0,0 +1,153 @@ +import os +import subprocess + +import pyblish.api +import filelink + + +class ExtractTranscode(pyblish.api.InstancePlugin): + """Extracts review movie from image sequence. + + Offset to get images to transcode from. + """ + + order = pyblish.api.ExtractorOrder + 0.1 + label = "Transcode" + optional = True + families = ["review"] + + def find_previous_index(self, index, indexes): + """Finds the closest previous value in a list from a value.""" + + data = [] + for i in indexes: + if i >= index: + continue + data.append(index - i) + + return indexes[data.index(min(data))] + + def process(self, instance): + + if "collection" in instance.data.keys(): + self.process_image(instance) + + if "output_path" in instance.data.keys(): + self.process_movie(instance) + + def process_image(self, instance): + + collection = instance.data.get("collection", []) + + if not list(collection): + msg = "Skipping \"{0}\" because no frames was found." + self.log.warning(msg.format(instance.data["name"])) + return + + # Temporary fill the missing frames. + missing = collection.holes() + if not collection.is_contiguous(): + pattern = collection.format("{head}{padding}{tail}") + for index in missing.indexes: + dst = pattern % index + src_index = self.find_previous_index( + index, list(collection.indexes) + ) + src = pattern % src_index + + filelink.create(src, dst) + + # Generate args. + # Has to be yuv420p for compatibility with older players and smooth + # playback. This does come with a sacrifice of more visible banding + # issues. + # -crf 18 is visually lossless. + args = [ + "ffmpeg", "-y", + "-start_number", str(min(collection.indexes)), + "-framerate", str(instance.context.data["framerate"]), + "-i", collection.format("{head}{padding}{tail}"), + "-pix_fmt", "yuv420p", + "-crf", "18", + "-timecode", "00:00:00:01", + "-vframes", + str(max(collection.indexes) - min(collection.indexes) + 1), + "-vf", + "scale=trunc(iw/2)*2:trunc(ih/2)*2", + ] + + if instance.data.get("baked_colorspace_movie"): + args = [ + "ffmpeg", "-y", + "-i", instance.data["baked_colorspace_movie"], + "-pix_fmt", "yuv420p", + "-crf", "18", + "-timecode", "00:00:00:01", + ] + + args.append(collection.format("{head}.mov")) + + self.log.debug("Executing args: {0}".format(args)) + + # Can't use subprocess.check_output, cause Houdini doesn't like that. + p = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, + cwd=os.path.dirname(args[-1]) + ) + + output = p.communicate()[0] + + # Remove temporary frame fillers + for f in missing: + os.remove(f) + + if p.returncode != 0: + raise ValueError(output) + + self.log.debug(output) + + def process_movie(self, instance): + # Generate args. + # Has to be yuv420p for compatibility with older players and smooth + # playback. This does come with a sacrifice of more visible banding + # issues. + args = [ + "ffmpeg", "-y", + "-i", instance.data["output_path"], + "-pix_fmt", "yuv420p", + "-crf", "18", + "-timecode", "00:00:00:01", + ] + + if instance.data.get("baked_colorspace_movie"): + args = [ + "ffmpeg", "-y", + "-i", instance.data["baked_colorspace_movie"], + "-pix_fmt", "yuv420p", + "-crf", "18", + "-timecode", "00:00:00:01", + ] + + split = os.path.splitext(instance.data["output_path"]) + args.append(split[0] + "_review.mov") + + self.log.debug("Executing args: {0}".format(args)) + + # Can't use subprocess.check_output, cause Houdini doesn't like that. + p = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, + cwd=os.path.dirname(args[-1]) + ) + + output = p.communicate()[0] + + if p.returncode != 0: + raise ValueError(output) + + self.log.debug(output) diff --git a/pype/plugins/nuke/_publish_unused/extract_nuke_baked_colorspace.py b/pype/plugins/nuke/_publish_unused/extract_nuke_baked_colorspace.py new file mode 100644 index 0000000000..f2561bd7c5 --- /dev/null +++ b/pype/plugins/nuke/_publish_unused/extract_nuke_baked_colorspace.py @@ -0,0 +1,107 @@ +import os +import tempfile +import shutil + +import nuke + +import pyblish.api + + +class ExtractNukeBakedColorspace(pyblish.api.InstancePlugin): + """Extracts movie with baked in luts + + V:\Remote Apps\ffmpeg\bin>ffmpeg -y -i + V:/FUGA/VFX_OUT/VFX_070010/v02/VFX_070010_comp_v02._baked.mov + -pix_fmt yuv420p + -crf 18 + -timecode 00:00:00:01 + V:/FUGA/VFX_OUT/VFX_070010/v02/VFX_070010_comp_v02..mov + + """ + + order = pyblish.api.ExtractorOrder + label = "Baked Colorspace" + optional = True + families = ["review"] + hosts = ["nuke"] + + def process(self, instance): + + if "collection" not in instance.data.keys(): + return + + # Store selection + selection = [i for i in nuke.allNodes() if i["selected"].getValue()] + + # Deselect all nodes to prevent external connections + [i["selected"].setValue(False) for i in nuke.allNodes()] + + temporary_nodes = [] + + # Create nodes + first_frame = min(instance.data["collection"].indexes) + last_frame = max(instance.data["collection"].indexes) + + temp_dir = tempfile.mkdtemp() + for f in instance.data["collection"]: + shutil.copy(f, os.path.join(temp_dir, os.path.basename(f))) + + node = previous_node = nuke.createNode("Read") + node["file"].setValue( + os.path.join(temp_dir, + os.path.basename(instance.data["collection"].format( + "{head}{padding}{tail}"))).replace("\\", "/")) + + node["first"].setValue(first_frame) + node["origfirst"].setValue(first_frame) + node["last"].setValue(last_frame) + node["origlast"].setValue(last_frame) + temporary_nodes.append(node) + + reformat_node = nuke.createNode("Reformat") + reformat_node["format"].setValue("HD_1080") + reformat_node["resize"].setValue("fit") + reformat_node["filter"].setValue("Lanczos6") + reformat_node["black_outside"].setValue(True) + reformat_node.setInput(0, previous_node) + previous_node = reformat_node + temporary_nodes.append(reformat_node) + + viewer_process_node = nuke.ViewerProcess.node() + dag_node = None + if viewer_process_node: + dag_node = nuke.createNode(viewer_process_node.Class()) + dag_node.setInput(0, previous_node) + previous_node = dag_node + temporary_nodes.append(dag_node) + # Copy viewer process values + excludedKnobs = ["name", "xpos", "ypos"] + for item in viewer_process_node.knobs().keys(): + if item not in excludedKnobs and item in dag_node.knobs(): + x1 = viewer_process_node[item] + x2 = dag_node[item] + x2.fromScript(x1.toScript(False)) + else: + self.log.warning("No viewer node found.") + + write_node = nuke.createNode("Write") + path = instance.data["collection"].format("{head}_baked.mov") + instance.data["baked_colorspace_movie"] = path + write_node["file"].setValue(path.replace("\\", "/")) + write_node["file_type"].setValue("mov") + write_node["raw"].setValue(1) + write_node.setInput(0, previous_node) + temporary_nodes.append(write_node) + + # Render frames + nuke.execute(write_node.name(), int(first_frame), int(last_frame)) + + # Clean up + for node in temporary_nodes: + nuke.delete(node) + + shutil.rmtree(temp_dir) + + # Restore selection + [i["selected"].setValue(False) for i in nuke.allNodes()] + [i["selected"].setValue(True) for i in selection]