From 227095a4f710ac6afd43f0a7e8b296f188cf20be Mon Sep 17 00:00:00 2001 From: David Luevano Alvarado Date: Tue, 31 May 2022 21:11:46 -0600 Subject: add working gif maker --- src/addons/GifMaker/GifRecorder.gd | 205 +++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 src/addons/GifMaker/GifRecorder.gd (limited to 'src/addons/GifMaker/GifRecorder.gd') 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) -- cgit v1.2.3-54-g00ecf