Last active
April 12, 2024 15:26
-
-
Save grassmunk/ee533129b66247d2b2ddbba43bbf9dd7 to your computer and use it in GitHub Desktop.
Converts Microsoft Animated cursors ico/cur to png
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import sys | |
import os | |
import subprocess | |
import shutil | |
# Properly convert a Microsoft ANI file to Icon and PNG | |
# The filename is made up of: [icon file name]_[rate in jiffies]_[sequence].png | |
# GPL 3.0 | |
def convert_icon_files(icon_filename): | |
try: | |
convert_path = subprocess.check_output(["which", "convert"]).strip() | |
except subprocess.CalledProcessError: | |
print("ERROR: You need imagemagick installed to use this script.") | |
exit(1) | |
png_file = icon_filename[:-4] + ".png" | |
print("\t{:<21} {}".format(icon_filename, png_file)) | |
args = [ | |
convert_path, | |
icon_filename, | |
png_file | |
] | |
subprocess.check_call(args) | |
if os.path.isfile(png_file[:-4]+"-0.png"): | |
shutil.move(png_file[:-4]+"-0.png", png_file[:-4]+".png") | |
def make_x11_cursors(icons_file, seq=False, rate=False, filename = "tmp_file"): | |
print("[Icons]") | |
icon_num = 1 | |
icon_file_names = [] | |
for icon in icons_file: | |
if len(icon) == 0: | |
# Skip empty icons | |
continue | |
icon_type = int.from_bytes(icon[2:4],"little") | |
number_of_images = int.from_bytes(icon[4:6],"little") | |
if icon_type == 1: | |
ext = ".ico" | |
else: | |
ext = ".cur" | |
path_to_ani, ani_file_name = os.path.split(filename) | |
if len(path_to_ani) > 0: | |
path_to_ani = path_to_ani + "/" | |
icon_name = os.path.splitext(ani_file_name)[0].replace(" ","_") | |
if seq: | |
num = "_{:02}".format(seq[icon_num-1]) | |
else: | |
num = "_{:02}".format(icon_num) | |
if rate and type(rate) is list: | |
jif = "_{}".format(rate[icon_num-1]) | |
elif rate: | |
jif = "_{}".format(rate) | |
else: | |
jif = "_{}".format(10) | |
icon_file = icon_name + jif + num + ext | |
print("\t{:<21} {}".format(icon_file, path_to_ani)) | |
icon_num += 1 | |
icon_file_names.append(path_to_ani+icon_file) | |
f = open(path_to_ani+icon_file,"wb") | |
f.write(icon) | |
f.close() | |
return icon_file_names | |
def parse_ani(file_name): | |
# convert an ani file to a list of bytearray icons | |
# input: ani file location/name | |
f = open(file_name,'rb') | |
ani_file = f.read() | |
f.close() | |
ani_bytes = bytearray(ani_file) | |
loc = ani_bytes.find("anih".encode()) + 8 | |
anih = { | |
"size" : 0, | |
"num_frames" : 0, | |
"num_steps" : 0, | |
"width" : 0, | |
"height" : 0, | |
"bit_count" : 0, | |
"num_planes" : 0, | |
"jif_rate" : 0, | |
"flags" : 0 | |
} | |
for i in anih: | |
anih[i] = int.from_bytes(ani_bytes[loc:loc+4],"little") | |
loc +=4 | |
print("[ANI Header]") | |
for i in anih: | |
print("\t{:<30} {}".format(i,anih[i])) | |
rate_length = anih["jif_rate"] | |
if ani_bytes.find("rate".encode()) > -1: | |
rate_length = [] | |
print("\n[Rate]") | |
loc = ani_bytes.find("rate".encode()) + 4 | |
rate_size = int.from_bytes(ani_bytes[loc:loc+4],"little") | |
loc += 4 | |
for i in range(anih["num_steps"]): | |
rate_length.append(int.from_bytes(ani_bytes[loc:loc+4],"little")) | |
loc += 4 | |
print("\t{}".format(rate_length)) | |
seq_length = False | |
if ani_bytes.find("seq ".encode()) > -1: | |
seq_length = [] | |
print("\n[Seq]") | |
loc = ani_bytes.find("seq ".encode()) + 4 | |
seq_size = int.from_bytes(ani_bytes[loc:loc+4],"little") | |
loc += 4 | |
for i in range(anih["num_steps"]): | |
seq_length.append(int.from_bytes(ani_bytes[loc:loc+4],"little")) | |
loc += 4 | |
print("\t{}".format(seq_length)) | |
# Now find the icons | |
loc = ani_bytes.find("LIST".encode()) + 4 | |
num_icons = int.from_bytes(ani_bytes[loc:loc+4],"little") | |
loc = ani_bytes.find("fram".encode()) + 8 | |
icons = [] | |
count = 0 | |
## At first icon | |
for i in range(anih["num_steps"]): | |
icon_size = int.from_bytes(ani_bytes[loc:loc+4],"little") | |
icon = ani_bytes[loc+4:(loc+4)+icon_size] | |
icons.append(icon) | |
loc = loc + icon_size + 8 | |
count = count + 1 | |
return icons, seq_length, rate_length | |
def main(): | |
print("ANI File Parser") | |
if len(sys.argv) < 2: | |
print("Error, you must supply an ani file") | |
sys.exit(-1) | |
file_name = sys.argv[1] | |
icons, seq, rate = parse_ani(file_name) | |
icon_file_names = make_x11_cursors(icons, seq, rate, file_name) | |
print("\n[Convert]") | |
for i in icon_file_names: | |
convert_icon_files(i) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment