diff --git a/boot_to_gif.py b/boot_to_gif.py new file mode 100644 index 0000000..ee7022c --- /dev/null +++ b/boot_to_gif.py @@ -0,0 +1,109 @@ +''' +project-name=GIF TO BOOT ANIMATION +DATE=30-01-2024 +DEVLOPER=SHASHAN SONI +https://github.com/soni-shashan/ +''' + +from customtkinter import * +from tkinter import filedialog +import threading +import sys +import os +import shutil +import signal +import create_boot_animation + +def goTo(): + if create_boot_animation.create(file_path,width,hight,fps,'OUTPUT'): + os.remove('OUTPUT/desc.txt') + shutil.rmtree('OUTPUT/part0') + information.destroy() + app.quit() + +def click_sumbit(): + global width,hight,fps + if width_entry.get()!="": + width=width_entry.get() + if not isinstance(width,int): + width=int(width) + else: + width=1024 + if hight_entry.get()!="": + hight=hight_entry.get() + if not isinstance(hight,int): + hight=int(hight) + else: + hight=600 + if fps_entry.get()!="": + fps=fps_entry.get() + if not isinstance(fps,int): + fps=int(fps) + else: + fps=60 + + global command,information,file_path + file_path=file_path.replace("/","\\\\") + command=f"{sys._MEIPASS}/python {sys._MEIPASS}/create_boot_animation.py \"{file_path}\" {width} {hight} {fps} output -zip" + label.destroy() + width_text.destroy() + width_entry.destroy() + hight_text.destroy() + hight_entry.destroy() + fps_label.destroy() + fps_entry.destroy() + create_btn.destroy() + information=CTkLabel(app,text="Please wait!! it may take 5-10 seconds.",fg_color="transparent",font=('arial',16)) + information.place(relx=0.5,rely=0.5,anchor="center") + therde=threading.Thread(target=goTo) + therde.start() + +def select_gif_file(): + global file_path,width_entry,hight_entry,fps_entry,label,width_text,hight_text,fps_entry,create_btn,fps_label + file_path = filedialog.askopenfilename(filetypes=[("GIF files", "*.gif")]) + # Do something with the selected GIF file path (e.g., display it, process it, etc.) + if file_path!="": + print("Selected GIF file: ", file_path) + btn.destroy() + + label = CTkLabel(app, text="Selected GIF file: "+ file_path, fg_color="transparent",font=('arial',14)) + label.place(relx=0.5, rely=0.1, anchor="center") + + width_text=CTkLabel(app,text="WIDTH (DEFUALT: 1024)",fg_color="transparent",font=('arial',16)) + width_text.place(relx=0.5,rely=0.15,anchor="center") + + width_entry=CTkEntry(app,width=170,text_color="#000000") + width_entry.place(relx=0.5,rely=0.22,anchor="center") + + hight_text=CTkLabel(app,text="HIGHT (DEFUALT: 600)",fg_color="transparent",font=('arial',16)) + hight_text.place(relx=0.5,rely=0.3,anchor="center") + + hight_entry=CTkEntry(app,width=170,text_color="#000000") + hight_entry.place(relx=0.5,rely=0.37,anchor="center") + + fps_label=CTkLabel(app,text="FPS (DEFUALT: 60)",fg_color="transparent",font=('arial',16)) + fps_label.place(relx=0.5,rely=0.45,anchor="center") + + fps_entry=CTkEntry(app,width=170,text_color="#000000") + fps_entry.place(relx=0.5,rely=0.52,anchor="center") + + create_btn=CTkButton(master=app,text='CREATE',corner_radius=20,fg_color="#C850C0",hover_color="#4158D0",border_color="#000000",border_width=2,width=150,height=50,command=click_sumbit) + create_btn.place(relx=0.5,rely=0.65,anchor="center") + width_entry.focus_set() + +def on_closing(): + os.kill(os.getpid(), signal.SIGINT) + sys.exit() + +if __name__=='__main__': + app=CTk() + app.geometry("512x512") + app.minsize(512,512) + app.maxsize(512,512) + app.title('GIF TO BOOT ANIMATITON') + app.protocol("WM_DELETE_WINDOW", on_closing) + set_appearance_mode("light") + app.iconbitmap(sys._MEIPASS+"\loading.ico") + btn=CTkButton(master=app,text='Select Gif',corner_radius=20,fg_color="#C850C0",hover_color="#4158D0",border_color="#000000",border_width=2,width=150,height=50,command=select_gif_file) + btn.place(relx=0.5,rely=0.1,anchor="center") + app.mainloop() \ No newline at end of file diff --git a/create_boot_animation.py b/create_boot_animation.py new file mode 100644 index 0000000..1d19e46 --- /dev/null +++ b/create_boot_animation.py @@ -0,0 +1,117 @@ +import argparse +import os +import tempfile +import zipfile +from PIL import Image +import gifextract + +def create(source, width, height, fps, save_to): + zip=True + source_dir = "" + temp_dir = None + if os.path.isdir(source): + source_dir = source + elif os.path.isfile(source) and get_extension(source) == "gif": + temp_dir = tempfile.TemporaryDirectory() + gifextract.processImage(source, temp_dir.name,width,height) + source_dir = temp_dir.name + else: + print("Error: invalid source path: " + source) + return + + images = get_images_paths(source_dir) + if len(images) <= 0: + print("Error: no images to process") + return + + if not os.path.exists(save_to): + os.makedirs(save_to) + + path_to_desc_file = create_desc_file(save_to, width, height, fps) + + dir_for_images = save_to + "/part0" + if not os.path.exists(dir_for_images): + os.makedirs(dir_for_images) + + count = 0 + for img in images: + count = transform_images(img, count, width, height, dir_for_images) + + with open(path_to_desc_file, "a") as f: + print("p 1 0 part0", file=f) + + if zip is True: + zip_file = zipfile.ZipFile(save_to + "/bootanimation.zip", mode="w", + compression=zipfile.ZIP_STORED) + + zip_file.write(path_to_desc_file, + arcname=os.path.basename(path_to_desc_file)) + + zip_dir(dir_for_images, zip_file) + zip_file.close() + + print("Done") + return True + + + +def get_extension(t_path): + path_parts = str.split(t_path, '.') + extension = path_parts[-1:][0] + extension = extension.lower() + return extension + + +def get_images_paths(t_folder): + if not os.path.isdir(t_folder): + return list() + + image_extensions = ("jpg", "jpeg", "bmp", "png", "tiff") + images = list() + entries = os.listdir(t_folder) + for entry in entries: + file_path = os.path.join(t_folder, entry) + extension = get_extension(file_path) + if os.path.isfile(file_path) and extension in image_extensions: + images.append(file_path) + + images.sort() + return images + + +def create_desc_file(t_folder, width, height, fps): + file_name = t_folder + "/desc.txt" + fd = open(file_name, mode="w+") + print("{} {} {}".format(width, height, fps), file=fd) + return file_name + + +def transform_images(t_img_path, t_count, width, height, save_to_path): + original_img = Image.open(t_img_path) + + # Scale image + width_percent = (width / float(original_img.width)) + height_size = int((float(original_img.height) * float(width_percent))) + original_img = original_img.resize((width, height_size), Image.LANCZOS) + + result_image = Image.new("RGB", (width, height), "white") + + width_pos = 0 + height_pos = int(height / 2 - original_img.height / 2) + result_image.paste(original_img, (width_pos, height_pos)) + + result_img_name = "{0:0{width}}.png".format(t_count, width=5) + result_img_path = save_to_path + "/" + result_img_name + result_image.save(result_img_path) + t_count += 1 + return t_count + + +def zip_dir(t_path, zip_file): + path_head, last_dir = os.path.split(t_path) + images = get_images_paths(t_path) + for img in images: + img_path_in_zip = last_dir + "/" + os.path.basename(img) + zip_file.write(img, arcname=img_path_in_zip, + compress_type=zipfile.ZIP_STORED) + diff --git a/gifextract.py b/gifextract.py new file mode 100644 index 0000000..aff292e --- /dev/null +++ b/gifextract.py @@ -0,0 +1,67 @@ +from PIL import Image +import os + + + +def analyseImage(path): + """ + Pre-process pass over the image to determine the mode (full or additive). + Necessary as assessing single frames isn't reliable. Need to know the mode + before processing all frames. + """ + im = Image.open(path) + results = { + 'size': im.size, + 'mode': 'full', + } + + try: + while True: + if im.tile: + tile = im.tile[0] + update_region = tile[1] + update_region_dimensions = update_region[2:] + if update_region_dimensions != im.size: + results['mode'] = 'partial' + break + + im.seek(im.tell() + 1) + except EOFError: + pass + return results + + +def processImage(path, output_folder,new_width, new_height): + """ + Iterate the GIF, extracting each frame. + """ + if not isinstance(new_width, int): + new_width=int(new_width) + if not isinstance(new_height, int): + new_height=int(new_height) + mode = analyseImage(path)['mode'] + gif = Image.open(path) + + # Create the output folder if it doesn't exist + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + for frame_number in range(gif.n_frames): + gif.seek(frame_number) + frame = gif.convert("RGBA") + + adjusted_frame_number = (frame_number - 1) % gif.n_frames + + resized_frame = frame.resize((new_width, new_height), Image.BOX) + + new_frame = Image.new('RGBA', (new_width,new_height)) + + if mode == 'partial': + new_frame.paste(resized_frame) + + new_frame.paste(resized_frame, (0, 0), resized_frame) + + result_frame_path = os.path.join(output_folder, f"{adjusted_frame_number:05d}.png") + new_frame.save(result_frame_path, 'PNG') + + gif.close() \ No newline at end of file diff --git a/loading.ico b/loading.ico new file mode 100644 index 0000000..3abb6fc Binary files /dev/null and b/loading.ico differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..21784ed --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +customtkinter==5.2.2 +Pillow==10.2.0