summaryrefslogtreecommitdiff
path: root/src/addons/GifMaker/GifRecorder.gd
diff options
context:
space:
mode:
Diffstat (limited to 'src/addons/GifMaker/GifRecorder.gd')
-rw-r--r--src/addons/GifMaker/GifRecorder.gd205
1 files changed, 205 insertions, 0 deletions
diff --git a/src/addons/GifMaker/GifRecorder.gd b/src/addons/GifMaker/GifRecorder.gd
new file mode 100644
index 0000000..44e30ed
--- /dev/null
+++ b/src/addons/GifMaker/GifRecorder.gd
@@ -0,0 +1,205 @@
+extends Viewport
+class_name GifRecorder, 'res://addons/GifMaker/gif.svg'
+
+signal record_past_buffer_filled
+signal encoding_progress(percentage, frames_done)
+signal done_encoding
+
+const GIFExporter = preload('res://addons/GifMaker/godot-gdgifexporter/gdgifexporter/exporter.gd')
+const MedianCut = preload('res://addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/median_cut.gd')
+const Uniform = preload('res://addons/GifMaker/godot-gdgifexporter/gdgifexporter/quantization/uniform.gd')
+
+enum RenderType {
+ RENDER_3D,
+ RENDER_2D
+}
+
+enum RecordType {
+ RECORD_PAST,
+ RECORD
+}
+
+enum Framerate {
+ FPS_100 = 1,
+ FPS_50 = 2,
+ FPS_33 = 3,
+ FPS_25 = 4,
+ FPS_20 = 5,
+ FPS_10 = 10,
+ FPS_8 = 12,
+ FPS_4 = 24,
+ FPS_2 = 48,
+ FPS_1 = 100
+}
+
+enum Quantization {
+ MEDIAN_CUT,
+ UNIFORM
+}
+
+# TODO: Re-write get_properties_list to fancify input
+# TODO: Perhaps find a way to render the viewport in editor
+# TODO: Record a lil' video with an example Godot project
+
+export(int, 'Render 3D', 'Render 2D') var render_type = RenderType.RENDER_3D setget set_render_type
+export(RecordType) var record_type = RecordType.RECORD_PAST
+export var seconds = 6.28 setget set_seconds
+export(Framerate) var framerate = Framerate.FPS_25 setget set_framerate
+export(Quantization) var quantization = Quantization.UNIFORM
+export var autostart = false
+
+export(NodePath) var capture_node_path
+onready var capture_node = get_node(capture_node_path)
+
+export var preview = false
+export var preview_render = false
+export(NodePath) var preview_path
+onready var preview_node = get_node(preview_path)
+
+var frame_amount = 0
+var frame_passed = 0
+var frames = []
+var exporter
+var frame_timer
+var thread
+
+func _ready():
+ frame_timer = Timer.new()
+ add_child(frame_timer)
+
+ assert(capture_node, 'No capture node detected, please add a Camera3D or GifRectangle to get started')
+
+ self.framerate = framerate
+ self.seconds = seconds
+ self.render_type = render_type
+
+ frame_timer.connect('timeout', self, 'capture')
+
+ if autostart:
+ start()
+
+func start():
+ frame_timer.start()
+
+func stop():
+ frame_timer.stop()
+
+func clear():
+ frames = []
+
+func render(metadata = null):
+ stop()
+
+ exporter = GIFExporter.new(size.x, size.y)
+
+ if OS.can_use_threads():
+ thread = Thread.new()
+ thread.start(self, 'encode', metadata)
+ while thread.is_alive():
+ yield(get_tree(), 'idle_frame')
+
+ return thread.wait_to_finish()
+ else:
+ return encode(metadata)
+
+func encode(metadata = null):
+ var frames_done = 0
+ var quantization_method = \
+ Uniform if quantization == Quantization.UNIFORM else MedianCut
+
+ for frame in frames:
+ emit_signal('encoding_progress', float(frames_done) / frames.size(), frames_done)
+ frame.convert(Image.FORMAT_RGBA8)
+ exporter.add_frame(frame, framerate, quantization_method)
+ frames_done += 1
+
+ if preview and preview_render:
+ var texture = ImageTexture.new()
+ texture.create_from_image(frame)
+ preview_node.texture = texture
+
+ if metadata:
+ exporter.add_comment_ext('GIF_MAKER::' + var2str(metadata))
+
+ exporter.add_comment_ext('🙋 Made with GifMaker by Bram Dingelstad')
+
+ emit_signal('encoding_progress', 1.0, frames_done)
+ emit_signal('done_encoding')
+ return exporter.export_file_data()
+
+func render_to_file(file_path, metadata = null):
+ var buffer = render(metadata)
+ if buffer is GDScriptFunctionState:
+ buffer = yield(buffer, 'completed')
+
+ var file = File.new()
+ file.open(file_path, File.WRITE)
+ file.store_buffer(buffer)
+ file.close()
+
+func capture():
+ var frame = Image.new()
+ var screenshot
+ match render_type:
+ RenderType.RENDER_2D:
+ screenshot = get_tree().root.get_texture().get_data()
+ screenshot.flip_y()
+ RenderType.RENDER_3D:
+ screenshot = get_texture().get_data()
+ if not render_target_v_flip:
+ screenshot.flip_y()
+
+ frame.create(size.x, size.y, false, screenshot.get_format())
+ frame.blit_rect(
+ screenshot,
+ Rect2(capture_node.rect_position if render_type == RenderType.RENDER_2D else Vector2.ZERO, size),
+ Vector2.ZERO
+ )
+ frames.append(frame)
+
+ match record_type:
+ RecordType.RECORD_PAST:
+ while frames.size() > frame_amount:
+ frames.pop_front()
+ emit_signal('record_past_buffer_filled')
+
+ if preview:
+ var texture = ImageTexture.new()
+ texture.create_from_image(frame)
+ preview_node.texture = texture
+
+func _process(delta):
+ render_target_update_mode = UPDATE_DISABLED if render_type == RenderType.RENDER_2D else UPDATE_ALWAYS
+
+ match render_type:
+ RenderType.RENDER_2D:
+ size = capture_node.rect_size
+
+ RenderType.RENDER_3D:
+ $Camera.transform = capture_node.transform
+ $Camera.fov = $Camera.fov
+
+ # TODO: Sync more properties like culling mask, projection, etc
+
+func update_frame_amount():
+ frame_amount = 100 / framerate * seconds
+
+func set_framerate(_framerate):
+ framerate = _framerate
+ if is_inside_tree():
+ frame_timer.wait_time = 1.0 / (100.0 / framerate)
+ update_frame_amount()
+
+func set_seconds(_seconds):
+ seconds = _seconds
+ update_frame_amount()
+
+func set_render_type(_render_type):
+ render_type = _render_type
+
+ match render_type:
+ RenderType.RENDER_3D:
+ var shadow_capture_node = capture_node.duplicate()
+ shadow_capture_node.name = 'Camera'
+ shadow_capture_node.script = null
+ add_child(shadow_capture_node)