1 # SPDX-FileCopyrightText: 2022-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
10 from datetime
import datetime
11 from bpy
.types
import Operator
12 from .utils
import get_keyframe_list
14 # ------------------------------------------------------
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
37 # Extension by image format
48 "OPEN_EXR_MULTILAYER": ".exr",
55 # --------------------------------------------------------------------
56 # Format an int adding 4 zero padding
57 # --------------------------------------------------------------------
58 def format_to4(self
, value
):
61 # --------------------------------------------------------------------
62 # Add frames every N frames
63 # --------------------------------------------------------------------
64 def add_missing_frames(self
, sq
, step
, keyframe_list
):
66 lk
= len(keyframe_list
)
72 for i
in range(0, lk
- 1):
73 dist
= keyframe_list
[i
+ 1] - keyframe_list
[i
]
75 delta
= int(dist
/ step
)
77 for x
in range(1, delta
):
78 missing
.append(keyframe_list
[i
] + (step
* e
))
81 keyframe_list
.extend(missing
)
84 # ------------------------------
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
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
:
118 if sq
.type == 'SCENE' and is_video_output
and sq
.parent_meta():
120 if sq
.type == 'SCENE':
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.
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
)
135 if os
.path
.isfile(file_path
) or os
.path
.islink(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' %
145 # Render Meta Strips (Only video)
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
)
155 filepath
= os
.path
.join(rootpath
, meta_name
)
157 if image_settings
.file_format
== 'FFMPEG':
158 ext
= self
.video_ext
[scene
.render
.ffmpeg
.format
]
162 if not filepath
.endswith(ext
):
165 scene
.render
.use_file_extension
= False
166 scene
.render
.filepath
= filepath
169 bpy
.ops
.render
.render(animation
=True)
171 # Add video to add meta strip later
172 if scene
.storypencil_add_render_strip
:
174 [filepath
, meta
.frame_start
+ meta
.frame_offset_start
])
176 # Read all scene strips and render the output (No META)
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
)
198 root_folder
= rootpath
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
:
207 keyframe
= key
+ sq
.frame_start
208 if scene
.use_preview_range
:
209 if keyframe
< scene
.frame_preview_start
:
211 if keyframe
> scene
.frame_preview_end
:
214 if keyframe
< scene
.frame_start
:
216 if keyframe
> scene
.frame_end
:
218 # For frame name use only the number
219 if scene
.storypencil_render_numbering
== 'FRAME':
221 framename
= strip_name
+ '.' + self
.format_to4(key
)
225 framename
= strip_name
+ '.' + \
226 self
.format_to4(frame_nrr
)
228 filepath
= os
.path
.join(root_folder
, framename
)
229 scene
.render
.filepath
= filepath
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
)
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
,
254 {"name": framename
+ image_ext
}],
255 frame_start
=int(frame_start
),
256 frame_end
=int(frame_end
),
259 print("Render:" + strip_name
+ "/" + strip_scene
.name
)
260 print("Video From:", scene
.frame_start
,
261 "To", scene
.frame_end
)
263 filepath
= os
.path
.join(rootpath
, strip_name
)
265 if image_settings
.file_format
== 'FFMPEG':
266 ext
= self
.video_ext
[scene
.render
.ffmpeg
.format
]
270 if not filepath
.endswith(ext
):
273 scene
.render
.use_file_extension
= False
274 scene
.render
.filepath
= filepath
277 bpy
.ops
.render
.render(animation
=True)
279 # Add video to add strip later
280 if scene
.storypencil_add_render_strip
:
282 [filepath
, sq
.frame_start
+ sq
.frame_offset_start
])
284 # Add pending video Strips
286 bpy
.ops
.sequencer
.movie_strip_add(filepath
=vid
[0],
287 frame_start
=int(vid
[1]),
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')
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')