Skip to content

Instantly share code, notes, and snippets.

@grassmunk
Last active April 12, 2024 15:26
Show Gist options
  • Save grassmunk/ee533129b66247d2b2ddbba43bbf9dd7 to your computer and use it in GitHub Desktop.
Save grassmunk/ee533129b66247d2b2ddbba43bbf9dd7 to your computer and use it in GitHub Desktop.
Converts Microsoft Animated cursors ico/cur to png
#!/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