Initial Commit
Initial commit of Code Base.
This commit is contained in:
parent
293b1213e1
commit
c11a4ebbc2
653 changed files with 36893 additions and 1 deletions
|
|
@ -0,0 +1,52 @@
|
|||
@tool
|
||||
extends EditorImportPlugin
|
||||
|
||||
class_name GifImporterImagemagick
|
||||
|
||||
enum Presets { DEFAULT }
|
||||
|
||||
func _get_importer_name() -> String:
|
||||
return "kani_dev.imagemagick"
|
||||
|
||||
func _get_visible_name() -> String:
|
||||
return "SpriteFrames (ImageMagick)"
|
||||
|
||||
func _get_recognized_extensions() -> PackedStringArray:
|
||||
return ["gif", "webp"]
|
||||
|
||||
func _get_save_extension() -> String:
|
||||
return "res"
|
||||
|
||||
func _get_resource_type() -> String:
|
||||
return "SpriteFrames"
|
||||
|
||||
func _get_priority() -> float:
|
||||
return 100.0
|
||||
|
||||
func _get_preset_count() -> int:
|
||||
return Presets.size()
|
||||
|
||||
func _get_preset_name(preset_index: int) -> String:
|
||||
return "Default"
|
||||
|
||||
func _get_import_options(path: String, preset_index: int) -> Array[Dictionary]:
|
||||
return []
|
||||
|
||||
func _get_import_order() -> int:
|
||||
return 0
|
||||
|
||||
func _get_option_visibility(path: String, option_name: StringName, options: Dictionary) -> bool:
|
||||
return true
|
||||
|
||||
func _import(source_file: String, save_path: String, options: Dictionary, platform_variants: Array[String], gen_files: Array[String]) -> Error:
|
||||
var dumper = ImageMagickConverter.new()
|
||||
|
||||
var tex = await dumper.dump_and_convert(source_file, [], "")
|
||||
if tex:
|
||||
return ResourceSaver.save(
|
||||
tex,
|
||||
"%s.%s" % [save_path, _get_save_extension()],
|
||||
ResourceSaver.SaverFlags.FLAG_COMPRESS
|
||||
)
|
||||
push_error("failed to import %s" % source_file)
|
||||
return OK;
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://chveatddlbp1x
|
||||
174
addons/twitcher/media/imagemagick/image_magick_converter.gd
Normal file
174
addons/twitcher/media/imagemagick/image_magick_converter.gd
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
@tool
|
||||
extends RefCounted
|
||||
|
||||
class_name ImageMagickConverter
|
||||
|
||||
static var _log: TwitchLogger = TwitchLogger.new("ImageMagickConverter")
|
||||
|
||||
## Current conversion in progress (key: path | value: mutex)
|
||||
var converting: Dictionary = {}
|
||||
var fallback_texture: Texture2D = preload("res://addons/twitcher/assets/fallback_texture.tres")
|
||||
## Path to the imagemagic executable for example 'magick' when its in windows path
|
||||
var imagemagic_path: String
|
||||
|
||||
static var delete_mutex: Mutex = Mutex.new()
|
||||
static var folder_to_delete: Array[String] = []
|
||||
|
||||
## After that amount of files it starts to delete them
|
||||
const DELETE_COUNT = 10
|
||||
|
||||
## Converts a packed byte array to a SpirteFrames and writes it out to the destination path
|
||||
##
|
||||
## The byte array must represent an animated gif, webp, or any imagemagick supported format
|
||||
## it dumps it into a binary resource consisting of PNG frames.
|
||||
##
|
||||
## The resource is automatically added to the ResourceLoader cache as the input path value
|
||||
func dump_and_convert(path: String, buffer_in: PackedByteArray = [], output = "%s.res" % path, parallel = false) -> SpriteFrames:
|
||||
var thread: Thread = Thread.new()
|
||||
var buffer: PackedByteArray = buffer_in.duplicate()
|
||||
var mutex: Mutex
|
||||
if parallel:
|
||||
mutex = converting.get(path, Mutex.new())
|
||||
converting[path] = mutex
|
||||
|
||||
var err = thread.start(_do_work.bind(path, buffer, output, mutex))
|
||||
assert(err == OK, "could not start thread")
|
||||
|
||||
# don't block the main thread while loading
|
||||
while not thread.is_started() or thread.is_alive():
|
||||
await Engine.get_main_loop().process_frame
|
||||
|
||||
var tex: SpriteFrames = thread.wait_to_finish()
|
||||
if parallel:
|
||||
mutex.unlock()
|
||||
converting.erase(path)
|
||||
|
||||
if not output.is_empty():
|
||||
_save_converted_file(tex, output)
|
||||
return tex
|
||||
|
||||
|
||||
func _do_work(path: String, buffer: PackedByteArray, output: String, mutex: Mutex) -> SpriteFrames:
|
||||
if mutex != null:
|
||||
mutex.lock()
|
||||
# load from cache if another thread already completed converting this same resource
|
||||
if not output.is_empty() and ResourceLoader.has_cached(output):
|
||||
return ResourceLoader.load(output)
|
||||
|
||||
# dump the buffer
|
||||
if FileAccess.file_exists(path):
|
||||
_log.i("File found at %s, loading it instead of using the buffer." % path)
|
||||
buffer = FileAccess.get_file_as_bytes(path)
|
||||
else:
|
||||
DirAccess.make_dir_recursive_absolute(path.get_base_dir())
|
||||
var f = FileAccess.open(path, FileAccess.WRITE)
|
||||
if f == null:
|
||||
_log.e("Can't open file %s cause of %s" %[ path, FileAccess.get_open_error()])
|
||||
f.store_buffer(buffer)
|
||||
f.close()
|
||||
|
||||
var frame_delays: Array[int] = _get_frame_delay(path)
|
||||
var folder_path: String = _create_temp_filename()
|
||||
if not _extract_images(path, folder_path):
|
||||
return _create_fallback_texture()
|
||||
var sprite_frames: SpriteFrames = _build_frames(folder_path, frame_delays)
|
||||
if not output.is_empty():
|
||||
sprite_frames.take_over_path(output)
|
||||
|
||||
# delete the temp directory
|
||||
delete_mutex.lock()
|
||||
folder_to_delete.append(folder_path)
|
||||
delete_mutex.unlock()
|
||||
|
||||
_cleanup()
|
||||
return sprite_frames
|
||||
|
||||
|
||||
## Saves the texture to the output path
|
||||
func _save_converted_file(tex: SpriteFrames, output: String):
|
||||
if not output.is_empty() and tex:
|
||||
ResourceSaver.save(tex, output, ResourceSaver.SaverFlags.FLAG_COMPRESS)
|
||||
tex.take_over_path(output)
|
||||
|
||||
|
||||
func _create_unique_key(length: int = 8) -> String:
|
||||
var uniq = ""
|
||||
for i in range(length):
|
||||
uniq += "%d" % [randi() % 10]
|
||||
return uniq
|
||||
|
||||
|
||||
## Creates a folder to store the extracted images (needs the / at the end!)
|
||||
func _create_temp_filename() -> String:
|
||||
var folder_path: String = ""
|
||||
var uniq = _create_unique_key()
|
||||
if Engine.is_editor_hint():
|
||||
folder_path = "res://.godot/magick_tmp/%s_%d/" % [uniq, Time.get_unix_time_from_system()]
|
||||
else:
|
||||
folder_path = "user://.magick_tmp/%s_%d/" % [uniq, Time.get_unix_time_from_system()]
|
||||
|
||||
_log.i("Create temp folder")
|
||||
DirAccess.make_dir_recursive_absolute(folder_path)
|
||||
return folder_path
|
||||
|
||||
|
||||
## Extracts all delays from the file in seconds
|
||||
func _get_frame_delay(file: String) -> Array[int]:
|
||||
var out = []
|
||||
var glob_path = ProjectSettings.globalize_path(file)
|
||||
OS.execute(imagemagic_path, [ glob_path, "-format", "%T\\n", "info:" ], out)
|
||||
var frame_delays: Array[int] = []
|
||||
for delay in out[0].split("\n"):
|
||||
# convert x100 to x1000(ms)
|
||||
frame_delays.append(delay.to_int() * 10)
|
||||
return frame_delays
|
||||
|
||||
|
||||
## Extracts all images from the file and saves them to folder path
|
||||
func _extract_images(file: String, target_folder: String) -> bool:
|
||||
var out = []
|
||||
var glob_file_path = ProjectSettings.globalize_path(file)
|
||||
var glob_extracted_file_path = ProjectSettings.globalize_path(target_folder + "%04d.png")
|
||||
var code = OS.execute(imagemagic_path, [ "convert", "-coalesce", glob_file_path, glob_extracted_file_path ], out, true)
|
||||
if code != 0:
|
||||
_log.e("unable to convert: %s" % "\n".join(out))
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func _create_fallback_texture():
|
||||
var sprite_frames = SpriteFrames.new()
|
||||
sprite_frames.add_frame(&"default", fallback_texture)
|
||||
return sprite_frames
|
||||
|
||||
|
||||
func _build_frames(folder_path: String, frame_delays: Array[int]):
|
||||
_log.i("Build Frames")
|
||||
var frames = DirAccess.get_files_at(folder_path)
|
||||
if len(frames) == 0:
|
||||
return _create_fallback_texture()
|
||||
|
||||
var sprite_frames: SpriteFrames = SpriteFrames.new()
|
||||
for filepath in frames:
|
||||
var idx = filepath.substr(0, filepath.rfind(".")).to_int()
|
||||
var delay = frame_delays[idx] / 1000.0
|
||||
var image = Image.new()
|
||||
var error = image.load(folder_path + filepath)
|
||||
if error != OK:
|
||||
return _create_fallback_texture()
|
||||
|
||||
var frame = ImageTexture.create_from_image(image)
|
||||
sprite_frames.add_frame(&"default", frame, delay)
|
||||
sprite_frames.set_animation_speed(&"default", 1)
|
||||
return sprite_frames
|
||||
|
||||
|
||||
## Cleans after DELETE_COUNT amount of folder entries
|
||||
func _cleanup(force: bool = false):
|
||||
if folder_to_delete.size() % DELETE_COUNT == 0 || force:
|
||||
delete_mutex.lock()
|
||||
for folder in folder_to_delete:
|
||||
var glob_folder = ProjectSettings.globalize_path(folder)
|
||||
OS.move_to_trash(glob_folder)
|
||||
folder_to_delete.clear()
|
||||
delete_mutex.unlock()
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://dydhjmb7b0ob0
|
||||
Loading…
Add table
Add a link
Reference in a new issue