Created
November 9, 2023 09:43
-
-
Save whoisryosuke/a55912549c00a8c51c2137f691416426 to your computer and use it in GitHub Desktop.
Blender Flamenco - Render Frames and Sleep - Select a series of frames, sleep duration, and batch size -- and it'll render all frames then in batches while sleeping between. Good for chunking renders and letting PC rest between segments in a single job (instead of queuing chunks manually)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: GPL-3.0-or-later | |
const JOB_TYPE = { | |
label: "Render All and Sleep", | |
settings: [ | |
// Settings for artists to determine: | |
{ | |
key: "frames", | |
type: "string", | |
required: true, | |
eval: "f'{C.scene.frame_start}-{C.scene.frame_end}'", | |
evalInfo: { | |
showLinkButton: true, | |
description: "Scene frame range", | |
}, | |
description: | |
"Frame range to render. Examples: '47', '1-30', '3, 5-10, 47-327'", | |
}, | |
{ | |
key: "chunk_size", | |
type: "int32", | |
default: 1, | |
description: "Number of frames to render in one Blender render task", | |
visible: "submission", | |
}, | |
// render_output_root + add_path_components determine the value of render_output_path. | |
{ | |
key: "render_output_root", | |
type: "string", | |
subtype: "dir_path", | |
required: true, | |
visible: "submission", | |
description: | |
"Base directory of where render output is stored. Will have some job-specific parts appended to it", | |
}, | |
{ | |
key: "add_path_components", | |
type: "int32", | |
required: true, | |
default: 0, | |
propargs: { min: 0, max: 32 }, | |
visible: "submission", | |
description: | |
"Number of path components of the current blend file to use in the render output path", | |
}, | |
{ | |
key: "render_output_path", | |
type: "string", | |
subtype: "file_path", | |
editable: false, | |
eval: "str(Path(abspath(settings.render_output_root), last_n_dir_parts(settings.add_path_components), jobname, '{timestamp}', '######'))", | |
description: "Final file path of where render output will be saved", | |
}, | |
// Sleep settings | |
{ | |
key: "sleep_duration_seconds", | |
type: "int32", | |
default: 30, | |
description: "Number of seconds to sleep between frame batches", | |
}, | |
{ | |
key: "batch_size", | |
type: "int32", | |
default: 25, | |
description: "Number of frames to render before going to sleep", | |
visible: "submission", | |
}, | |
// Automatically evaluated settings: | |
{ | |
key: "blendfile", | |
type: "string", | |
required: true, | |
description: "Path of the Blend file to render", | |
visible: "web", | |
}, | |
{ | |
key: "fps", | |
type: "float", | |
eval: "C.scene.render.fps / C.scene.render.fps_base", | |
visible: "hidden", | |
}, | |
{ | |
key: "format", | |
type: "string", | |
required: true, | |
eval: "C.scene.render.image_settings.file_format", | |
visible: "web", | |
}, | |
{ | |
key: "image_file_extension", | |
type: "string", | |
required: true, | |
eval: "C.scene.render.file_extension", | |
visible: "hidden", | |
description: "File extension used when rendering images", | |
}, | |
{ | |
key: "has_previews", | |
type: "bool", | |
required: false, | |
eval: "C.scene.render.image_settings.use_preview", | |
visible: "hidden", | |
description: "Whether Blender will render preview images.", | |
}, | |
], | |
}; | |
// Set of scene.render.image_settings.file_format values that produce | |
// files which FFmpeg is known not to handle as input. | |
const ffmpegIncompatibleImageFormats = new Set([ | |
"EXR", | |
"MULTILAYER", // Old CLI-style format indicators | |
"OPEN_EXR", | |
"OPEN_EXR_MULTILAYER", // DNA values for these formats. | |
]); | |
// File formats that would cause rendering to video. | |
// This is not supported by this job type. | |
const videoFormats = ["FFMPEG", "AVI_RAW", "AVI_JPEG"]; | |
function compileJob(job) { | |
print("Blender Render job submitted"); | |
print("job: ", job); | |
const settings = job.settings; | |
if (videoFormats.indexOf(settings.format) >= 0) { | |
throw `This job type only renders images, and not "${settings.format}"`; | |
} | |
const renderOutput = renderOutputPath(job); | |
// Make sure that when the job is investigated later, it shows the | |
// actually-used render output: | |
settings.render_output_path = renderOutput; | |
const renderDir = path.dirname(renderOutput); | |
const renderTasks = authorRenderTasks(settings, renderDir, renderOutput); | |
function newSleepTask() { | |
const sleepTask = author.Task("sleep", "misc"); | |
sleepTask.addCommand( | |
author.Command("sleep", { | |
duration_in_seconds: settings.sleep_duration_seconds, | |
}) | |
); | |
return sleepTask; | |
} | |
let sleepTask = newSleepTask(); | |
let loopCount = 0; | |
for (const rt of renderTasks) { | |
job.addTask(rt); | |
// We add a sleep task in between a certain number of render tasks (aka rendering 1 frame usually) | |
if (sleepTask) { | |
// If there is a video task, all other tasks have to be done first. | |
sleepTask.addDependency(rt); | |
loopCount = loopCount + 1; | |
if (loopCount == settings.batch_size) { | |
loopCount = 0; | |
job.addTask(sleepTask); | |
sleepTask = newSleepTask(); | |
} | |
} | |
} | |
} | |
// Do field replacement on the render output path. | |
function renderOutputPath(job) { | |
let path = job.settings.render_output_path; | |
if (!path) { | |
throw "no render_output_path setting!"; | |
} | |
return path.replace(/{([^}]+)}/g, (match, group0) => { | |
switch (group0) { | |
case "timestamp": | |
return formatTimestampLocal(job.created); | |
default: | |
return match; | |
} | |
}); | |
} | |
function authorRenderTasks(settings, renderDir, renderOutput) { | |
print("authorRenderTasks(", renderDir, renderOutput, ")"); | |
print("frames", settings.frames); | |
let renderTasks = []; | |
let chunks = frameChunker(settings.frames, settings.chunk_size); | |
for (let chunk of chunks) { | |
const task = author.Task(`render-${chunk}`, "blender"); | |
const command = author.Command("blender-render", { | |
exe: "{blender}", | |
exeArgs: "{blenderArgs}", | |
argsBefore: [], | |
blendfile: settings.blendfile, | |
args: [ | |
"--render-output", | |
path.join(renderDir, path.basename(renderOutput)), | |
"--render-format", | |
settings.format, | |
"--render-frame", | |
chunk.replaceAll("-", ".."), // Convert to Blender frame range notation. | |
], | |
}); | |
task.addCommand(command); | |
renderTasks.push(task); | |
} | |
return renderTasks; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment