Campbell Barton
e8da6131fd
Move copyright text to SPDX-FileCopyrightText or set to the Blender Foundation so "make check_licenses" now runs without warnings.
315 lines
12 KiB
Python
315 lines
12 KiB
Python
# SPDX-FileCopyrightText: 2022-2023 Blender Foundation
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
import bpy
|
|
import os
|
|
import shutil
|
|
import sys
|
|
|
|
from datetime import datetime
|
|
from bpy.types import Operator
|
|
from .utils import get_keyframe_list
|
|
|
|
# ------------------------------------------------------
|
|
# Button: Render VSE
|
|
# ------------------------------------------------------
|
|
|
|
|
|
class STORYPENCIL_OT_RenderAction(Operator):
|
|
bl_idname = "storypencil.render_vse"
|
|
bl_label = "Render Strips"
|
|
bl_description = "Render VSE strips"
|
|
|
|
# Extension by FFMPEG container type
|
|
video_ext = {
|
|
"MPEG1": ".mpg",
|
|
"MPEG2": ".dvd",
|
|
"MPEG4": ".mp4",
|
|
"AVI": ".avi",
|
|
"QUICKTIME": ".mov",
|
|
"DV": ".dv",
|
|
"OGG": ".ogv",
|
|
"MKV": ".mkv",
|
|
"FLASH": ".flv",
|
|
"WEBM": ".webm"
|
|
}
|
|
# Extension by image format
|
|
image_ext = {
|
|
"BMP": ".bmp",
|
|
"IRIS": ".rgb",
|
|
"PNG": ".png",
|
|
"JPEG": ".jpg",
|
|
"JPEG2000": ".jp2",
|
|
"TARGA": ".tga",
|
|
"TARGA_RAW": ".tga",
|
|
"CINEON": ".cin",
|
|
"DPX": ".dpx",
|
|
"OPEN_EXR_MULTILAYER": ".exr",
|
|
"OPEN_EXR": ".exr",
|
|
"HDR": ".hdr",
|
|
"TIFF": ".tif",
|
|
"WEBP": ".webp"
|
|
}
|
|
|
|
# --------------------------------------------------------------------
|
|
# Format an int adding 4 zero padding
|
|
# --------------------------------------------------------------------
|
|
def format_to4(self, value):
|
|
return f"{value:04}"
|
|
|
|
# --------------------------------------------------------------------
|
|
# Add frames every N frames
|
|
# --------------------------------------------------------------------
|
|
def add_missing_frames(self, sq, step, keyframe_list):
|
|
missing = []
|
|
lk = len(keyframe_list)
|
|
if lk == 0:
|
|
return
|
|
|
|
# Add mid frames
|
|
if step > 0:
|
|
for i in range(0, lk - 1):
|
|
dist = keyframe_list[i + 1] - keyframe_list[i]
|
|
if dist > step:
|
|
delta = int(dist / step)
|
|
e = 1
|
|
for x in range(1, delta):
|
|
missing.append(keyframe_list[i] + (step * e))
|
|
e += 1
|
|
|
|
keyframe_list.extend(missing)
|
|
keyframe_list.sort()
|
|
|
|
# ------------------------------
|
|
# Execute
|
|
# ------------------------------
|
|
def execute(self, context):
|
|
scene = bpy.context.scene
|
|
image_settings = scene.render.image_settings
|
|
is_video_output = image_settings.file_format in {
|
|
'FFMPEG', 'AVI_JPEG', 'AVI_RAW'}
|
|
step = scene.storypencil_render_step
|
|
|
|
sequences = scene.sequence_editor.sequences_all
|
|
prv_start = scene.frame_start
|
|
prv_end = scene.frame_end
|
|
prv_frame = bpy.context.scene.frame_current
|
|
|
|
prv_path = scene.render.filepath
|
|
prv_format = image_settings.file_format
|
|
prv_use_file_extension = scene.render.use_file_extension
|
|
prv_ffmpeg_format = scene.render.ffmpeg.format
|
|
rootpath = bpy.path.abspath(scene.storypencil_render_render_path)
|
|
only_selected = scene.storypencil_render_onlyselected
|
|
channel = scene.storypencil_render_channel
|
|
|
|
context.window.cursor_set('WAIT')
|
|
|
|
# Create list of selected strips because the selection is changed when adding new strips
|
|
Strips = []
|
|
Metas = []
|
|
for sq in sequences:
|
|
if sq.type in ('SCENE', 'META'):
|
|
if only_selected is False or sq.select is True:
|
|
if sq.type == 'META' and is_video_output:
|
|
Metas.append(sq)
|
|
continue
|
|
if sq.type == 'SCENE' and is_video_output and sq.parent_meta():
|
|
continue
|
|
if sq.type == 'SCENE':
|
|
Strips.append(sq)
|
|
|
|
# Sort strips
|
|
Strips = sorted(Strips, key=lambda strip: strip.frame_start)
|
|
|
|
# For video, clear BL_proxy folder because sometimes the video
|
|
# is not rendered as expected if this folder has data.
|
|
# This ensure the output video is correct.
|
|
if is_video_output:
|
|
proxy_folder = os.path.join(rootpath, "BL_proxy")
|
|
if os.path.exists(proxy_folder):
|
|
for filename in os.listdir(proxy_folder):
|
|
file_path = os.path.join(proxy_folder, filename)
|
|
try:
|
|
if os.path.isfile(file_path) or os.path.islink(file_path):
|
|
os.unlink(file_path)
|
|
elif os.path.isdir(file_path):
|
|
shutil.rmtree(file_path)
|
|
except Exception as e:
|
|
print('Failed to delete %s. Reason: %s' %
|
|
(file_path, e))
|
|
|
|
try:
|
|
Videos = []
|
|
# Render Meta Strips (Only video)
|
|
for meta in Metas:
|
|
meta_name = meta.name
|
|
scene.frame_start = int(meta.frame_start + meta.frame_offset_start)
|
|
scene.frame_end = int(meta.frame_start + meta.frame_final_duration - 1)
|
|
|
|
print("Meta:" + meta_name)
|
|
print("Video From:", scene.frame_start,
|
|
"To", scene.frame_end)
|
|
# Video
|
|
filepath = os.path.join(rootpath, meta_name)
|
|
|
|
if image_settings.file_format == 'FFMPEG':
|
|
ext = self.video_ext[scene.render.ffmpeg.format]
|
|
else:
|
|
ext = '.avi'
|
|
|
|
if not filepath.endswith(ext):
|
|
filepath += ext
|
|
|
|
scene.render.use_file_extension = False
|
|
scene.render.filepath = filepath
|
|
|
|
# Render Animation
|
|
bpy.ops.render.render(animation=True)
|
|
|
|
# Add video to add meta strip later
|
|
if scene.storypencil_add_render_strip:
|
|
Videos.append(
|
|
[filepath, meta.frame_start + meta.frame_offset_start])
|
|
|
|
# Read all scene strips and render the output (No META)
|
|
for sq in Strips:
|
|
strip_name = sq.name
|
|
strip_scene = sq.scene
|
|
scene.frame_start = int(sq.frame_start + sq.frame_offset_start)
|
|
scene.frame_end = int(scene.frame_start + sq.frame_final_duration - 1) # Image
|
|
if is_video_output is False:
|
|
# Get list of any keyframe
|
|
strip_start = sq.frame_offset_start
|
|
if strip_start < strip_scene.frame_start:
|
|
strip_start = strip_scene.frame_start
|
|
|
|
strip_end = strip_start + sq.frame_final_duration - 1
|
|
keyframe_list = get_keyframe_list(
|
|
strip_scene, strip_start, strip_end)
|
|
self.add_missing_frames(sq, step, keyframe_list)
|
|
|
|
scene.render.use_file_extension = True
|
|
foldername = strip_name
|
|
if scene.storypencil_add_render_byfolder is True:
|
|
root_folder = os.path.join(rootpath, foldername)
|
|
else:
|
|
root_folder = rootpath
|
|
|
|
frame_nrr = 0
|
|
print("Render:" + strip_name + "/" + strip_scene.name)
|
|
print("Image From:", strip_start, "To", strip_end)
|
|
for key in range(int(strip_start), int(strip_end) + 1):
|
|
if key not in keyframe_list:
|
|
continue
|
|
|
|
keyframe = key + sq.frame_start
|
|
if scene.use_preview_range:
|
|
if keyframe < scene.frame_preview_start:
|
|
continue
|
|
if keyframe > scene.frame_preview_end:
|
|
break
|
|
else:
|
|
if keyframe < scene.frame_start:
|
|
continue
|
|
if keyframe > scene.frame_end:
|
|
break
|
|
# For frame name use only the number
|
|
if scene.storypencil_render_numbering == 'FRAME':
|
|
# Real
|
|
framename = strip_name + '.' + self.format_to4(key)
|
|
else:
|
|
# Consecutive
|
|
frame_nrr += 1
|
|
framename = strip_name + '.' + \
|
|
self.format_to4(frame_nrr)
|
|
|
|
filepath = os.path.join(root_folder, framename)
|
|
scene.render.filepath = filepath
|
|
|
|
# Render Frame
|
|
scene.frame_set(int(keyframe - 1.0), subframe=0.0)
|
|
bpy.ops.render.render(
|
|
animation=False, write_still=True)
|
|
|
|
# Add strip with the corresponding length
|
|
if scene.storypencil_add_render_strip:
|
|
frame_start = sq.frame_start + key - 1
|
|
index = keyframe_list.index(key)
|
|
if index < len(keyframe_list) - 1:
|
|
key_next = keyframe_list[index + 1]
|
|
frame_end = frame_start + (key_next - key)
|
|
else:
|
|
frame_end = scene.frame_end + 1
|
|
|
|
if index == 0 and frame_start > scene.frame_start:
|
|
frame_start = scene.frame_start
|
|
|
|
if frame_end < frame_start:
|
|
frame_end = frame_start
|
|
image_ext = self.image_ext[image_settings.file_format]
|
|
bpy.ops.sequencer.image_strip_add(directory=root_folder,
|
|
files=[
|
|
{"name": framename + image_ext}],
|
|
frame_start=int(frame_start),
|
|
frame_end=int(frame_end),
|
|
channel=channel)
|
|
else:
|
|
print("Render:" + strip_name + "/" + strip_scene.name)
|
|
print("Video From:", scene.frame_start,
|
|
"To", scene.frame_end)
|
|
# Video
|
|
filepath = os.path.join(rootpath, strip_name)
|
|
|
|
if image_settings.file_format == 'FFMPEG':
|
|
ext = self.video_ext[scene.render.ffmpeg.format]
|
|
else:
|
|
ext = '.avi'
|
|
|
|
if not filepath.endswith(ext):
|
|
filepath += ext
|
|
|
|
scene.render.use_file_extension = False
|
|
scene.render.filepath = filepath
|
|
|
|
# Render Animation
|
|
bpy.ops.render.render(animation=True)
|
|
|
|
# Add video to add strip later
|
|
if scene.storypencil_add_render_strip:
|
|
Videos.append(
|
|
[filepath, sq.frame_start + sq.frame_offset_start])
|
|
|
|
# Add pending video Strips
|
|
for vid in Videos:
|
|
bpy.ops.sequencer.movie_strip_add(filepath=vid[0],
|
|
frame_start=int(vid[1]),
|
|
channel=channel)
|
|
|
|
scene.frame_start = prv_start
|
|
scene.frame_end = prv_end
|
|
scene.render.use_file_extension = prv_use_file_extension
|
|
image_settings.file_format = prv_format
|
|
scene.render.ffmpeg.format = prv_ffmpeg_format
|
|
|
|
scene.render.filepath = prv_path
|
|
scene.frame_set(int(prv_frame))
|
|
|
|
context.window.cursor_set('DEFAULT')
|
|
|
|
return {'FINISHED'}
|
|
|
|
except:
|
|
print("Unexpected error:" + str(sys.exc_info()))
|
|
self.report({'ERROR'}, "Unable to render")
|
|
scene.frame_start = prv_start
|
|
scene.frame_end = prv_end
|
|
scene.render.use_file_extension = prv_use_file_extension
|
|
image_settings.file_format = prv_format
|
|
|
|
scene.render.filepath = prv_path
|
|
scene.frame_set(int(prv_frame))
|
|
context.window.cursor_set('DEFAULT')
|
|
return {'FINISHED'}
|