Merge branch 'blender-v4.0-release'
[blender-addons.git] / storypencil / render.py
blob17007073182467b2e55f5a92b89482750fabf8d7
1 # SPDX-FileCopyrightText: 2022-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import bpy
6 import os
7 import shutil
8 import sys
10 from datetime import datetime
11 from bpy.types import Operator
12 from .utils import get_keyframe_list
14 # ------------------------------------------------------
15 # Button: Render VSE
16 # ------------------------------------------------------
19 class STORYPENCIL_OT_RenderAction(Operator):
20 bl_idname = "storypencil.render_vse"
21 bl_label = "Render Strips"
22 bl_description = "Render VSE strips"
24 # Extension by FFMPEG container type
25 video_ext = {
26 "MPEG1": ".mpg",
27 "MPEG2": ".dvd",
28 "MPEG4": ".mp4",
29 "AVI": ".avi",
30 "QUICKTIME": ".mov",
31 "DV": ".dv",
32 "OGG": ".ogv",
33 "MKV": ".mkv",
34 "FLASH": ".flv",
35 "WEBM": ".webm"
37 # Extension by image format
38 image_ext = {
39 "BMP": ".bmp",
40 "IRIS": ".rgb",
41 "PNG": ".png",
42 "JPEG": ".jpg",
43 "JPEG2000": ".jp2",
44 "TARGA": ".tga",
45 "TARGA_RAW": ".tga",
46 "CINEON": ".cin",
47 "DPX": ".dpx",
48 "OPEN_EXR_MULTILAYER": ".exr",
49 "OPEN_EXR": ".exr",
50 "HDR": ".hdr",
51 "TIFF": ".tif",
52 "WEBP": ".webp"
55 # --------------------------------------------------------------------
56 # Format an int adding 4 zero padding
57 # --------------------------------------------------------------------
58 def format_to4(self, value):
59 return f"{value:04}"
61 # --------------------------------------------------------------------
62 # Add frames every N frames
63 # --------------------------------------------------------------------
64 def add_missing_frames(self, sq, step, keyframe_list):
65 missing = []
66 lk = len(keyframe_list)
67 if lk == 0:
68 return
70 # Add mid frames
71 if step > 0:
72 for i in range(0, lk - 1):
73 dist = keyframe_list[i + 1] - keyframe_list[i]
74 if dist > step:
75 delta = int(dist / step)
76 e = 1
77 for x in range(1, delta):
78 missing.append(keyframe_list[i] + (step * e))
79 e += 1
81 keyframe_list.extend(missing)
82 keyframe_list.sort()
84 # ------------------------------
85 # Execute
86 # ------------------------------
87 def execute(self, context):
88 scene = bpy.context.scene
89 image_settings = scene.render.image_settings
90 is_video_output = image_settings.file_format in {
91 'FFMPEG', 'AVI_JPEG', 'AVI_RAW'}
92 step = scene.storypencil_render_step
94 sequences = scene.sequence_editor.sequences_all
95 prv_start = scene.frame_start
96 prv_end = scene.frame_end
97 prv_frame = bpy.context.scene.frame_current
99 prv_path = scene.render.filepath
100 prv_format = image_settings.file_format
101 prv_use_file_extension = scene.render.use_file_extension
102 prv_ffmpeg_format = scene.render.ffmpeg.format
103 rootpath = bpy.path.abspath(scene.storypencil_render_render_path)
104 only_selected = scene.storypencil_render_onlyselected
105 channel = scene.storypencil_render_channel
107 context.window.cursor_set('WAIT')
109 # Create list of selected strips because the selection is changed when adding new strips
110 Strips = []
111 Metas = []
112 for sq in sequences:
113 if sq.type in ('SCENE', 'META'):
114 if only_selected is False or sq.select is True:
115 if sq.type == 'META' and is_video_output:
116 Metas.append(sq)
117 continue
118 if sq.type == 'SCENE' and is_video_output and sq.parent_meta():
119 continue
120 if sq.type == 'SCENE':
121 Strips.append(sq)
123 # Sort strips
124 Strips = sorted(Strips, key=lambda strip: strip.frame_start)
126 # For video, clear BL_proxy folder because sometimes the video
127 # is not rendered as expected if this folder has data.
128 # This ensure the output video is correct.
129 if is_video_output:
130 proxy_folder = os.path.join(rootpath, "BL_proxy")
131 if os.path.exists(proxy_folder):
132 for filename in os.listdir(proxy_folder):
133 file_path = os.path.join(proxy_folder, filename)
134 try:
135 if os.path.isfile(file_path) or os.path.islink(file_path):
136 os.unlink(file_path)
137 elif os.path.isdir(file_path):
138 shutil.rmtree(file_path)
139 except Exception as e:
140 print('Failed to delete %s. Reason: %s' %
141 (file_path, e))
143 try:
144 Videos = []
145 # Render Meta Strips (Only video)
146 for meta in Metas:
147 meta_name = meta.name
148 scene.frame_start = int(meta.frame_start + meta.frame_offset_start)
149 scene.frame_end = int(meta.frame_start + meta.frame_final_duration - 1)
151 print("Meta:" + meta_name)
152 print("Video From:", scene.frame_start,
153 "To", scene.frame_end)
154 # Video
155 filepath = os.path.join(rootpath, meta_name)
157 if image_settings.file_format == 'FFMPEG':
158 ext = self.video_ext[scene.render.ffmpeg.format]
159 else:
160 ext = '.avi'
162 if not filepath.endswith(ext):
163 filepath += ext
165 scene.render.use_file_extension = False
166 scene.render.filepath = filepath
168 # Render Animation
169 bpy.ops.render.render(animation=True)
171 # Add video to add meta strip later
172 if scene.storypencil_add_render_strip:
173 Videos.append(
174 [filepath, meta.frame_start + meta.frame_offset_start])
176 # Read all scene strips and render the output (No META)
177 for sq in Strips:
178 strip_name = sq.name
179 strip_scene = sq.scene
180 scene.frame_start = int(sq.frame_start + sq.frame_offset_start)
181 scene.frame_end = int(scene.frame_start + sq.frame_final_duration - 1) # Image
182 if is_video_output is False:
183 # Get list of any keyframe
184 strip_start = sq.frame_offset_start
185 if strip_start < strip_scene.frame_start:
186 strip_start = strip_scene.frame_start
188 strip_end = strip_start + sq.frame_final_duration - 1
189 keyframe_list = get_keyframe_list(
190 strip_scene, strip_start, strip_end)
191 self.add_missing_frames(sq, step, keyframe_list)
193 scene.render.use_file_extension = True
194 foldername = strip_name
195 if scene.storypencil_add_render_byfolder is True:
196 root_folder = os.path.join(rootpath, foldername)
197 else:
198 root_folder = rootpath
200 frame_nrr = 0
201 print("Render:" + strip_name + "/" + strip_scene.name)
202 print("Image From:", strip_start, "To", strip_end)
203 for key in range(int(strip_start), int(strip_end) + 1):
204 if key not in keyframe_list:
205 continue
207 keyframe = key + sq.frame_start
208 if scene.use_preview_range:
209 if keyframe < scene.frame_preview_start:
210 continue
211 if keyframe > scene.frame_preview_end:
212 break
213 else:
214 if keyframe < scene.frame_start:
215 continue
216 if keyframe > scene.frame_end:
217 break
218 # For frame name use only the number
219 if scene.storypencil_render_numbering == 'FRAME':
220 # Real
221 framename = strip_name + '.' + self.format_to4(key)
222 else:
223 # Consecutive
224 frame_nrr += 1
225 framename = strip_name + '.' + \
226 self.format_to4(frame_nrr)
228 filepath = os.path.join(root_folder, framename)
229 scene.render.filepath = filepath
231 # Render Frame
232 scene.frame_set(int(keyframe - 1.0), subframe=0.0)
233 bpy.ops.render.render(
234 animation=False, write_still=True)
236 # Add strip with the corresponding length
237 if scene.storypencil_add_render_strip:
238 frame_start = sq.frame_start + key - 1
239 index = keyframe_list.index(key)
240 if index < len(keyframe_list) - 1:
241 key_next = keyframe_list[index + 1]
242 frame_end = frame_start + (key_next - key)
243 else:
244 frame_end = scene.frame_end + 1
246 if index == 0 and frame_start > scene.frame_start:
247 frame_start = scene.frame_start
249 if frame_end < frame_start:
250 frame_end = frame_start
251 image_ext = self.image_ext[image_settings.file_format]
252 bpy.ops.sequencer.image_strip_add(directory=root_folder,
253 files=[
254 {"name": framename + image_ext}],
255 frame_start=int(frame_start),
256 frame_end=int(frame_end),
257 channel=channel)
258 else:
259 print("Render:" + strip_name + "/" + strip_scene.name)
260 print("Video From:", scene.frame_start,
261 "To", scene.frame_end)
262 # Video
263 filepath = os.path.join(rootpath, strip_name)
265 if image_settings.file_format == 'FFMPEG':
266 ext = self.video_ext[scene.render.ffmpeg.format]
267 else:
268 ext = '.avi'
270 if not filepath.endswith(ext):
271 filepath += ext
273 scene.render.use_file_extension = False
274 scene.render.filepath = filepath
276 # Render Animation
277 bpy.ops.render.render(animation=True)
279 # Add video to add strip later
280 if scene.storypencil_add_render_strip:
281 Videos.append(
282 [filepath, sq.frame_start + sq.frame_offset_start])
284 # Add pending video Strips
285 for vid in Videos:
286 bpy.ops.sequencer.movie_strip_add(filepath=vid[0],
287 frame_start=int(vid[1]),
288 channel=channel)
290 scene.frame_start = prv_start
291 scene.frame_end = prv_end
292 scene.render.use_file_extension = prv_use_file_extension
293 image_settings.file_format = prv_format
294 scene.render.ffmpeg.format = prv_ffmpeg_format
296 scene.render.filepath = prv_path
297 scene.frame_set(int(prv_frame))
299 context.window.cursor_set('DEFAULT')
301 return {'FINISHED'}
303 except:
304 print("Unexpected error:" + str(sys.exc_info()))
305 self.report({'ERROR'}, "Unable to render")
306 scene.frame_start = prv_start
307 scene.frame_end = prv_end
308 scene.render.use_file_extension = prv_use_file_extension
309 image_settings.file_format = prv_format
311 scene.render.filepath = prv_path
312 scene.frame_set(int(prv_frame))
313 context.window.cursor_set('DEFAULT')
314 return {'FINISHED'}