Uncategorized

MUZZLE FLASH, MY FRIEND

 

tl;dr: Locate and sort out your muzzle flashes and other stock footage elements quick and easy.

Face it, digital comping is not only about putting together the pieces of a Transformer, bringing back to life a roaring T-Rex, or giving to a lightsaber the right glowy touch… often it´s also about tackling some tedious, repetitive and highly time-consuming tasks that on top of that usually remain unnoticed and unaccounted for.
Thankfully, there is Python, and here is where being able to write some wise code really comes in handy :).
1 – The challenge
Consider this, you have been given the task of processing a library of stock footage in search of nice muzzle-flashes to use on a given show. Imagine there are around 100-150, 4k clips that you have to play and examine (either in Nuke or Nuke Studio, or Hiero or any other player) to eventually locate the frame or frames where the muzzles flashes are happening on.
 As you probably know, those footage are shot in the dark, so usually you would be watching at noisy black footage up until the moment that, voila, a muzzle flash shows up.

2 – Approach A (be -very- patient)

Ok, once one of those guys appear, you would ideally want to lock down the frame where this is happening. For such a thing, you can use a FrameHold Node in Nuke, a timeline marker in After Effects or just take a note on a notebook, to mention a few options. You would continue the same way up until you would have located all the muzzle flashes.

I leave you to reckon with how long any of these approaches would take…

3 – Aproach B (go -moderately- smart)

Let´s get this straight. What if you could, just by clicking on a button, automatically build a contact sheet displaying all the different muzzle flash elements there are, therefore enabling you to easily check out the different types, camera angles and so on, and then, once you have built that contact-sheet up, go to any of the individual files and extract all the muzzle flashes in that file?

Well, that, along with other fancy add-ons, is exactly what this script does.

4 – How it does it.

Honestly, this is a fairly simple code. Just a few functions, a class definition to help build a nuke Python Panel, and some global variables. In the form of “pseudo code”, here is the thing:

1 – analyze: all (selected) nodes, provided they are Read Nodes, to automatically detect, off of luminance intensities, on which frame or frames a muzzle flash is taking place.

2 – detect: depending on the so-called “build mode” (I´ll go over this later), it either drops a Framehold node each time a muzzle flash is detected or just one for the muzzle flash that has the maximum luminance intensity, thus locking down either all of them, or just the brightest one.

3 – build: with all detected muzzle flashes locked down with a Framehold node, the script then builds a contact sheet with all of them, and then drops a text node connected to each of the files displaying its name on the screen, automatically.

 And as an image is often worth more than a thousand words, this is how it would look like.

5 – How to use it.

Ok, with all that being said, now we should basically place the code where it can be reached, call its main function and then deal with a few options on a Nuke Panel. Let´s see how this works.

1 – distribute the code. As we know, when it comes to Python Scripts, we need to be able to import them in Nuke with the command “import module_name”. We can do this either by defining the PYTHON_PATH environment variable or simply by adding within the init.py file the folder structure where we will place our Gizmos, Plugins, Python Scripts and so on, using the command “nuke.pluginAddPath”. If none of this options sound familiar to you, then just drop the script on to the user´s .nuke folder and hope for the best.

2 – run the tool. Open the Nuke´s Script Editor and type “import module_name” and then type “module_name.main_function()”… Other than this basic way of importing a module and calling a module´s function from Nuke´s Script Editor, you can also implement the code on a Gizmo´s Custom Python Script Button, or create a Nuke menu from where to call your Python Scripts, or even set up a hotkey to easily access to the tool. I´ll leave the implementation part to you.
3 – deal with a few options. Right after running the tool, a pop-up Nuke Panel will appear displaying a few options.
– time range: classic “input/global/custom” pulldown menu. Enables you to analyze your files according to any of these options.

 – build mode: extract will get all the muzzle flashes in all selected clips. Build will get one muzzle flash per clip, exactly that with the maximum brightness.

As a rule of thumb, you probably want to run the tool with the “build” option first and then, once u have built a contact-sheet with a muzzle flash per clip, select a clip from which you would like to extract all the muzzle-flash instances and run the tool in “extract” mode.

important: If you find this usefull, it´s always better to create a Nuke menu from where to call the function.

code

#!/usr/bin/env python

import nuke
import nukescripts

print "importing nuke..."
print "importing nukescripts..."


########################################################################################################################

__author__ = "Boris Martinez Castillo"
__version__ = "1.0.1"
__maintainer__ = "Boris Martinez Castillo"
__email__ = "boris.vfx@outlook.com"

########################################################################################################################

START_FRAME = int(nuke.root()['first_frame'].value())
LAST_FRAME = int(nuke.root()['last_frame'].value())


def get_file_name(f):
    return f['file'].value().split('/')[-1]

def set_name(n):
    txt = nuke.createNode("Text2")
    msg = txt['message'].setValue(n)
    return txt

def content_finder():
    SEL = nuke.selectedNodes()  # HOLD ALL SELECTED NODES
    fh_sel_list = []
    txt_sel_list = []
    counter = 0

    for i in SEL:
        start_frame = i.firstFrame()
        end_frame = i.lastFrame()
        print start_frame
        print end_frame
        i['selected'].setValue('False')  # DE-SELECT CURRENT NODE FROM SELECTION LIST.

        cv = nuke.createNode("CurveTool")
        nuke.execute(cv, START_FRAME, LAST_FRAME)  # EXECUTES CURVE TOOL

        if type(get_stop_frame(cv)) == list:

            for e in get_stop_frame(cv):
                fh = nuke.createNode("FrameHold")
                fh.setInput(0, cv)
                fh['first_frame'].setValue(int(e))
                label = set_name(get_file_name(i))
                txt_sel_list.append(label.name())
                read_recolor(nuke.toNode(i.name()), 112656639)
            counter += 1
            print counter
        else:
            read_recolor(nuke.toNode(i.name()), 3238002943)
            counter += 1
        if counter == len(SEL):
            print "txt_sel_list: ", txt_sel_list
            return txt_sel_list

def content_finder_input():
    SEL = nuke.selectedNodes()  # HOLD ALL SELECTED NODES
    fh_sel_list = []
    txt_sel_list = []
    counter = 0

    for i in SEL:
        start_frame = i.firstFrame()
        end_frame = i.lastFrame()
        print start_frame
        print end_frame
        i['selected'].setValue('False')  # DE-SELECT CURRENT NODE FROM SELECTION LIST.

        cv = nuke.createNode("CurveTool")
        nuke.execute(cv, start_frame, end_frame)  # EXECUTES CURVE TOOL

        if type(get_stop_frame(cv)) == list:

            for e in get_stop_frame(cv):
                fh = nuke.createNode("FrameHold")
                fh.setInput(0, cv)
                fh['first_frame'].setValue(int(e))
                label = set_name(get_file_name(i))
                txt_sel_list.append(label.name())
                read_recolor(nuke.toNode(i.name()), 112656639)
            counter += 1
            print counter
        else:
            read_recolor(nuke.toNode(i.name()), 3238002943)
            counter += 1
        if counter == len(SEL):
            print "txt_sel_list: ", txt_sel_list
            return txt_sel_list

def content_finder_custom(sf,lf):
    SEL = nuke.selectedNodes()  # HOLD ALL SELECTED NODES
    fh_sel_list = []
    txt_sel_list = []
    counter = 0

    for i in SEL:
        start_frame = i.firstFrame()
        end_frame = i.lastFrame()
        print start_frame
        print end_frame
        i['selected'].setValue('False')  #  DE-SELECT CURRENT NODE FROM SELECTION LIST

        cv = nuke.createNode("CurveTool")
        nuke.execute(cv, sf, lf)  #  EXECUTES CURVE TOOL

        if type(get_stop_frame(cv)) == list:

            for e in get_stop_frame(cv):
                fh = nuke.createNode("FrameHold")
                fh.setInput(0, cv)
                fh['first_frame'].setValue(int(e))
                label = set_name(get_file_name(i))
                txt_sel_list.append(label.name())
                read_recolor(nuke.toNode(i.name()), 112656639)
            counter += 1
            print counter
        else:
            read_recolor(nuke.toNode(i.name()), 3238002943)
            counter += 1
        if counter == len(SEL):
            print "txt_sel_list: ", txt_sel_list
            return txt_sel_list

def content_finder_int():
    SEL = nuke.selectedNodes()  # HOLD ALL SELECTED NODES
    fh_sel_list = []
    txt_sel_list = []
    counter = 0

    for i in SEL:

        i['selected'].setValue('False')  # DE-SELECT CURRENT NODE FROM SELECTION LIST

        cv = nuke.createNode("CurveTool")
        nuke.execute(cv, START_FRAME, LAST_FRAME)  #  EXECUTES CURVE TOOL

        if type(get_stop_frame_int(cv)) == int:

            fh = nuke.createNode("FrameHold")
            fh['first_frame'].setValue(get_stop_frame_int(cv))
            label = set_name(get_file_name(i))
            txt_sel_list.append(label.name())
            read_recolor(nuke.toNode(i.name()), 112656639)
            counter += 1
            print counter
        else:
            read_recolor(nuke.toNode(i.name()), 3238002943)
            counter += 1
        if counter == len(SEL):
            print txt_sel_list
            return txt_sel_list

def content_finder_int_input():
    SEL = nuke.selectedNodes()  # HOLD ALL SELECTED NODES
    fh_sel_list = []
    txt_sel_list = []
    counter = 0

    for i in SEL:
        start_frame = i.firstFrame()
        end_frame = i.lastFrame()
        print start_frame
        print end_frame
        i['selected'].setValue('False')  # DE-SELECT CURRENT NODE FROM SELECTION LIST

        cv = nuke.createNode("CurveTool")
        nuke.execute(cv, start_frame, end_frame)  #  EXECUTES CURVE TOOL

        if type(get_stop_frame_int(cv)) == int:

            fh = nuke.createNode("FrameHold")
            fh['first_frame'].setValue(get_stop_frame_int(cv))
            label = set_name(get_file_name(i))
            txt_sel_list.append(label.name())
            read_recolor(nuke.toNode(i.name()), 112656639)
            counter += 1
            print counter
        else:
            read_recolor(nuke.toNode(i.name()), 3238002943)
            counter += 1
        if counter == len(SEL):
            print txt_sel_list
            return txt_sel_list

def content_finder_int_custom(sf,lf):
    SEL = nuke.selectedNodes()  # HOLD ALL SELECTED NODES
    fh_sel_list = []
    txt_sel_list = []
    counter = 0

    for i in SEL:
        start_frame = i.firstFrame()
        end_frame = i.lastFrame()
        print start_frame
        print end_frame
        i['selected'].setValue('False')  # DE-SELECT CURRENT NODE FROM SELECTION LIST

        cv = nuke.createNode("CurveTool")
        nuke.execute(cv, sf, lf)  # EXECUTES CURVE TOOL
        print
        if type(get_stop_frame_int(cv)) == int:

            fh = nuke.createNode("FrameHold")
            fh['first_frame'].setValue(get_stop_frame_int(cv))
            label = set_name(get_file_name(i))
            txt_sel_list.append(label.name())
            read_recolor(nuke.toNode(i.name()), 112656639)
            counter += 1
            print counter
        else:
            read_recolor(nuke.toNode(i.name()), 3238002943)
            counter += 1
        if counter == len(SEL):
            print txt_sel_list
            return txt_sel_list

def attach_contact_sheet(l):

    print "attach_contact_sheet list: ", l
    all_nodes = len(l)
   	
    [nuke.toNode(i)['selected'].setValue('True') for i in l]
    contact_sheet = nuke.createNode("ContactSheet")
    columns = int(contact_sheet["columns"].getValue())
    rows = (all_nodes / columns) + 1
    contact_sheet["rows"].setValue(rows)
    
	
def read_recolor(l,c):
    l['tile_color'].setValue(c)
    return

def get_other_muzzles(l,f,a):
    for i in l:
        if i > f:
            a.append(i)
    return a

def get_frames(anim,value,li):
    frame = 0
    for key in anim.keys():
        x_value = key.x
        y_value = key.y
        if y_value == value:
            frame = x_value
            li.append(frame)
            break
    return li

def get_stop_frame(node):
    anim_curve_x = node['intensitydata'].animation(0)
    y_list = []
    stop_frame = 0

    for key in anim_curve_x.keys():
        x_value = key.x
        y_value = key.y
        y_list.append(y_value)


    y_max_value = max(y_list) # CALCULATE MAX VALUE
    other_muzzles = []
    other_stop_frames = []

    print "max value found: ", y_max_value

    om = get_other_muzzles(y_list,0.05,other_muzzles)
    print "other_muzzles: ",om
    for i in om:
        get_frames(anim_curve_x,i,other_stop_frames)

    print "other_stop_frames: ", other_stop_frames

    if y_max_value < 0.05:
        return None

    for key in anim_curve_x.keys():
        x_value = key.x
        y_value = key.y
        if y_value == y_max_value:
            stop_frame = x_value
            break

    return other_stop_frames

def get_stop_frame_int(node):
    anim_curve_x = node['intensitydata'].animation(0)
    y_list = []
    stop_frame = 0

    for key in anim_curve_x.keys():
        x_value = key.x
        y_value = key.y
        y_list.append(y_value)


    y_max_value = max(y_list)  # CALCULATE MAX VALUE
    other_muzzles = []
    other_stop_frames = []

    print "max value found: ", y_max_value

    om = get_other_muzzles(y_list, 0.05, other_muzzles)
    print "other_muzzles: ", om
    for i in om:
        get_frames(anim_curve_x, i, other_stop_frames)

    print "other_stop_frames: ", other_stop_frames

    if y_max_value < 0.05:
        return None

    for key in anim_curve_x.keys():
        x_value = key.x
        y_value = key.y
        if y_value == y_max_value:
            stop_frame = x_value
            break

    return int(stop_frame)

def main_function():
    panel = modalPanel()
    if not panel.showModalDialog():
        print "script aborted"
        return
    else:
        print "script run"
        if panel.giveFrameRangeValue() == "global" and panel.analysis_mode.value() == "extract" :
            attach_contact_sheet(content_finder())
            return
        elif panel.giveFrameRangeValue() == "input" and panel.analysis_mode.value() == "extract":
            attach_contact_sheet(content_finder_input())
            return
        elif panel.giveFrameRangeValue() == "custom" and panel.analysis_mode.value() == "extract":
            range = panel.frame_display.value()
            start_frame = int(range.split("-")[0])
            end_frame = int(range.split("-")[-1])
            print start_frame, end_frame
            attach_contact_sheet(content_finder_custom(start_frame,end_frame))
            return
        elif  panel.giveFrameRangeValue() == "global" and panel.analysis_mode.value() == "contact sheet":
            attach_contact_sheet(content_finder_int())
            return
        elif panel.giveFrameRangeValue() == "input" and panel.analysis_mode.value() == "contact sheet":
            attach_contact_sheet(content_finder_int_input())
            return
        elif panel.giveFrameRangeValue() == "custom" and panel.analysis_mode.value() == "contact sheet":
            range = panel.frame_display.value()
            start_frame = int(range.split("-")[0])
            end_frame = int(range.split("-")[-1])
            print start_frame, end_frame
            attach_contact_sheet(content_finder_int_custom(start_frame, end_frame))
            return
        else:
            print "pass"
            return

#CREATE A PYTHON PANEL FOR DEALING WITH TIME RANGES

class modalPanel(nukescripts.PythonPanel):

    def __init__(self):
        nukescripts.PythonPanel.__init__(self,"get my muzzle")
        #CREATE KNOBS
        self.frame_range = nuke.Enumeration_Knob('fRange','frame range', ['global','input','custom'])
        self.analysis_mode = nuke.Enumeration_Knob('mode','build mode  ', ['contact sheet','extract'])
        self.frame_display = nuke.String_Knob("")
        self.frame_display.clearFlag(nuke.STARTLINE)
        self.author = nuke.Text_Knob("written by Boris Martinez")
        #ADD KNOBS
        for i in (self.frame_range,self.frame_display,self.analysis_mode,self.author):
            self.addKnob(i)
        #SET KNOB DEFAULT VALUES
        self.get_frame_range()

    def giveFrameRangeValue(self):
        return self.frame_range.value()

    def get_frame_range(self):
        if self.giveFrameRangeValue() == "global":
            first_frame = nuke.root().firstFrame()
            last_frame = nuke.root().lastFrame()
            txt = str(int(first_frame)) + '-' + str(int(last_frame))
            self.frame_display.setValue(txt)
        elif self.giveFrameRangeValue() == "input":
            print "here should come the read frame range"
            node = nuke.selectedNode()
            first_frame = node.firstFrame()
            last_frame = node.lastFrame()
            txt = str(int(first_frame)) + '-' + str(int(last_frame))
            self.frame_display.setValue(txt)
        elif self.giveFrameRangeValue() == "custom":
            self.frame_display.setValue("")
            print "here the user decides"

    def knobChanged(self,knob):
        if knob.name() == "fRange":
            self.get_frame_range()


if __name__ == "__main__":
    main_function()


Read more →

Batch Gizmo 2 Grizmo Creator

tl;dr: Batch convert your favourite Nuke Gizmos into “Grizmos”.

Hi folks,
 
So here I have a new little Python script for those who find a little boring converting Gizmos into “Grizmos” manually. Just in case, I´ll remind you that one of the best ways to make sure all your fancy Gizmos are going to render in the farm, or that a user that does not have a particular Gizmo can still open a comp that has it and work on it, is simply converting that Gizmo into a Group, thus getting the so-called  “Grizmo” (as Hugo Lévéille would call´em).
What this script does, in its first version, is basically creating a Grizmo per each Gizmo found in a passed directory. AKA a batch Gizmo-to-Group converter, or a batch Grizmo Creator… And only for Windows users, at the moment.
How to use it?
1 – Store and name the Python script in a folder that Nuke is going to look at when it comes to importing Python scripts. Try the command “print nuke.pluginPath()” in the script editor if you are not sure where your Nuke installation is looking for Plugins, Python Scripts and such.
2 – Import the script you have just saved from Nuke´s script editor like “import module_name“.
3 – Call the main()  function from the imported script like “module_name.main()“.
4 – If everything goes ok, you should immediately see a pop-up window asking for you to select the “Gizmos Path” directory. Navigate to the folder where you store all your fancy Gizmos and click ok.
5 – Now you should have as many Grizmos as Gizmos you had in the former directory, with the “_grizmed” extension added to the original name. For instance:
    – Cryptomatte.gizmo (original Gizmo)
    – Cryptomatte_grizmed.gizmo (Grizmed Gizmo)
6 – Enjoy your brand new fancy Grizmos :D

important: If you find this usefull, it´s always better to create a Nuke menu from where to call the function.

snippet
import nuke
import os
import nukescripts

def transform_line(line):
    """ basic character replacement function.
    :param line: a string to be passed to perform character replacement.
    :return: the line mutated with the new replaced character.
    """
    word_replacements = {'Gizmo': 'Group'}
    for key, value in word_replacements.iteritems():
        line = line.replace(key, value)
    return line


def gizmo_to_grizmo(i):
    """ this function creates a "grouped" .gizmo file.
    :param i: input file to open
    :return: None
    """
    o = str.split(i,".")[0] + "_grizmed" + "." + str.split(i,".")[-1]
    with open(o, "w") as output_file, open(i) as input_file:
        for line in input_file:
            output_file.write(transform_line(line))
    return None


def grizmo_my_folder(dir):
    """ this fuction parses a directory and applies the gizmo_to_grizmo to its files, if convenient.
       :param dir: directory to be parsed.
       :return: None
       """
    for subdir, dirs, files in os.walk(dir):
        for file in files:
            # print os.path.join(subdir, file)
            filepath = subdir + os.sep + file
            if "grizmed" in filepath:
                print "already grizmed"
                return
            elif filepath.endswith(".gizmo") and "grizmed" not in filepath:
                gizmo_to_grizmo(filepath)
                print "gizmos grizmed succesfully"
            else:
                print "no valid file found, bruh"
                return
            

class Panel(nukescripts.PythonPanel):
    def __init__(self, name):
        super(Panel, self).__init__(name)
        # self.setMinimumSize(500,500)
        self.path = nuke.File_Knob('gizmos_path', 'Gizmos Path')

        self.addKnob(self.path)

    def show_modal_dialog(self):
        nukescripts.PythonPanel.showModalDialog(self)


def main():
    """
    wrapper function. From here we open the Nuke Panel to find the Gizmo Directory and launch the rest of the program
    :return: None
    """
    p = Panel("Grizmo Maker")
    p.show_modal_dialog()
    path = p.path.getValue()
    grizmo_my_folder(path)

    if path:
        panel = nuke.message("Grizmification completed")
        print "Grizmification completed"
    else:
        panel = nuke.message("Grizmification aborted")
        print "Grizmification aborted"
    return


if __name__ == '__main__':
    main()
Read more →