1
$\begingroup$

I have made a movie clip Frame change detector in the compositor, which uses a less than node result to pass a frame or render black out during animation. As I have a boolean value from the node I want to use it to alter the rendered file save as name each time it is black, to a non unique name so that it will be overwritten. Thus I won't accumulate black frames only changed frames.

I first asked ChatGPT and it gave me this script with comments. It runs on my Mac but fails to write a sensible name for the black frames. It doesn't collect the original path to insert in the new file name. I'm not sure if its related to The OS or a mishandling of the

import bpy
import os

# This function will be called every time the frame changes
def update_overwrite(scene):
    # Get the current frame number
    frame = scene.frame_current
    
    # Get the compositor node tree
    tree = bpy.context.scene.node_tree
    
    # Get the node whose output we want to use as input
    # In this example, we use the less than operation on the alpha channel of an image
    # You can change this to any node you want
    node = tree.nodes["Less Than"]
    
    # Get the output socket of the node
    socket = node.outputs[0]
    
    # Evaluate the output value of the socket at the current frame
    # This will return a single value between 0 and 1
    value = socket.evaluate(frame)
    
    # Set the custom property to the output value
    scene["overwrite"] = value

# Register the function as a frame change handler
bpy.app.handlers.frame_change_pre.append(update_overwrite)

# Create a global variable to store the original file name
original_file_name = ""

# This function will be called before each render frame
def save_file_name(scene):
    # Get the current file name
    global original_file_name
    original_file_name = bpy.context.scene.render.filepath
    
    # Set the render file path to "overwrite_me"
    bpy.context.scene.render.filepath = os.path.join(os.getcwd(), "overwrite_me/")

# This function will be called after each render frame
def restore_file_name(scene):
    # Restore the original file name
    global original_file_name
    bpy.context.scene.render.filepath = original_file_name

# Register the functions as render handlers
bpy.app.handlers.render_pre.append(save_file_name)
bpy.app.handlers.render_post.append(restore_file_name)

I asked ChatGPT to revise the script so that it uses the original path instead of some installed directory, I also asked it to use an if else statement from the result of the less than node in case I had to flip the choice of frame to save.

import bpy
import os

# This function will be called before each render frame
def save_file_name(scene):
    # Get the current frame number
    frame = scene.frame_current
    
    # Get the compositor node tree
    tree = bpy.context.scene.node_tree
    
    # Get the 'Less Than' node whose output we want to use as input
    node = tree.nodes["Less Than"]
    
    # Get the output socket of the node
    socket = node.outputs[0]
    
    # Evaluate the output value of the socket at the current frame
    value = socket.evaluate(frame)
    
    # Decide which value to use based on the output of the 'Less Than' node
    # If the output is 1.0, we use the alternate value, otherwise we use the original
    alternate_value = 1 if value == 1.0 else 0

    # Use the alternate value to set the render file path
    if alternate_value == 1:
        # Set the render file path to "overwrite_me"
        bpy.context.scene.render.filepath = os.path.join(os.path.dirname(bpy.context.scene.render.filepath), "overwrite_me")
    else:
        # Restore the original file path
        bpy.context.scene.render.filepath = os.path.dirname(bpy.context.scene.render.filepath)

# Register the function as a render pre handler
bpy.app.handlers.render_pre.append(save_file_name)

I will include the blend file with the frame change detection, it will need movie clip that includes an edit to work

Here is the updated blend file with alternate script

$\endgroup$
11
  • 1
    $\begingroup$ this is just my personal opinion - but...i would recommend to not write, that you used chatgpt. It might be honest, but it sounds like "i am too lazy to do the work, so i used chatgpt and now i am searching for someone to repair it". And as you can see - chatgpt didn't really help you. I also would recommend learning python so that you can do that on your own. It is not too hard, but it takes time. And if you really wanna search for someone who does your work, maybe blenderartists.org is a better play to ask under "volunteer work" $\endgroup$
    – Chris
    Commented Mar 9 at 14:20
  • $\begingroup$ and another thing: if you don't pack resources before uploading, we don't get your movie clip and cannot really test it. $\endgroup$
    – Chris
    Commented Mar 9 at 14:24
  • 2
    $\begingroup$ Hello ! FWIW you can ask chatGPT to fix specific problems, too, once the script has been generated. $\endgroup$
    – Gorgious
    Commented Mar 9 at 15:08
  • $\begingroup$ Chris you cannot pack a movie in Blender only images $\endgroup$
    – 3pointedit
    Commented Mar 9 at 20:37
  • 1
    $\begingroup$ The scripts do not make sense and demonstrate a fundamental misunderstanding. value = socket.evaluate(frame) does not exist. $\endgroup$
    – unwave
    Commented Mar 10 at 2:13

1 Answer 1

2
$\begingroup$

Clarification

Not being able to access the Render Results pixels without saving to disk has been a long standing issue: Pixels and resolution not for multilayer EXR and Render Result #53768

The workaround with the Viewer Node does not work in the background mode: Viewer node pixels do not update when running blender in background-mode #91891

Using bpy.ops.render.render in a bpy.app.handlers.render_pre handler will crash blender when rendering animations.

The compositor does not update when changes are made in some the pre render type handlers.

bpy.app.handlers.frame_change_post and bpy.app.handlers.frame_change_pre handlers do not work when rendering animation

These and other issues make impossible to build a generic approach to the domain of the problems which the OP's post is a part of.

Having an automatic pre-rendering of images, that are then will be used to modify the scene before the main render and working in all cases, is possible but requires a custom research and non-trivial.

The solution to the specific problem

To access the render results you need to write the image to the disk. The File Output Node writes extra images to the disk along side with the Render Result. Here is an example on how to read that extra probe image and change the name of the main render file.

enter image description here

import os
import uuid

import bpy


def get_file_output_node():
    return bpy.context.scene.node_tree.nodes["File Output"]


def rename_frame_file(img_path):
    stem, extension = os.path.splitext(os.path.basename(img_path))
    new_img_path = os.path.join(os.path.dirname(img_path), 'overwrite_me_' + stem + extension)
#    new_img_path = os.path.join(os.path.dirname(img_path), 'overwrite_me' + extension)
    os.replace(img_path, new_img_path)


def get_probe_image_path(frame: int):
    """
    https://docs.blender.org/manual/en/latest/compositing/types/output/file_output.html
    {base path}/{file name}{frame number}.{extension}
    """

    scene = bpy.context.scene

    node = get_file_output_node()

    file_name = node.file_slots[0].path
    frame_number = str(frame).zfill(4)
    extension = scene.render.image_settings.file_format.lower()

    basename = f"{file_name}{frame_number}.{extension}"

    return os.path.join(node.base_path, basename)


def rename_render_image(scene, depsgraph):

    frame = scene.frame_current

    img_path = bpy.context.scene.render.frame_path(frame=frame)
    prob_path = get_probe_image_path(frame)

    prob_image = bpy.data.images.load(filepath=prob_path)

    the_value = bool(prob_image.pixels[0])

    if the_value:
        print("RENAMED:", frame)
        rename_frame_file(img_path)

    bpy.data.images.remove(prob_image)


def name_probes_folder(scene, depsgraph):

    probes_dir = os.path.join(bpy.app.tempdir, uuid.uuid1().hex)
    os.makedirs(probes_dir, exist_ok=True)

    get_file_output_node().base_path = probes_dir
    
    bpy.context.scene.render.filepath = probes_dir + os.sep
    
    os.startfile(probes_dir)


bpy.app.handlers.render_init.clear()
bpy.app.handlers.render_write.clear()

bpy.app.handlers.render_init.append(name_probes_folder)
bpy.app.handlers.render_write.append(rename_render_image)


if bpy.app.background:
    import atexit

    def pause():
        if os.name == 'nt':
            os.system('pause')
        else:
            input('Press Enter to exit...')

    atexit.register(pause)

To run in cmd: blender -b --factory-startup --python-exit-code 1 D:\Desktop\write_simular_frame_mask_image_4.blend --python-text "the_script" -a

PS

Blender lacks an ability be backfed and interrogated during a process in an easy manner. And this looks like a XY problem because if you just want to remove duplicate frames from a video you would use ffmpeg.

$\endgroup$
1
  • $\begingroup$ I see so I need to render everything before testing its value, this is not what I had expected, I could just run a bat script in the render folder to delete small files. Thanks for your advice though. Not sure I can technically accept it as an answer as it probably needs a bit more clarity around what you are suggesting is wrong with the question. Perhaps add in what has been written in the comments about Blender not being able to do Conditional rendering before file save? $\endgroup$
    – 3pointedit
    Commented Mar 13 at 0:49

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .