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