工具介绍
工具名称:Python打包窗口图标丢失修复工具(PackTool)
工具版本:1.01
功能介绍:
1. 能够将ico文件转换为二进制内容,然后将二进制内容保存在py文件中
2. 能够将py文件中对应的二进制内容转换为ico文件
3. 能够作为API进行直接调用
工具截图:
下载链接:GitHub下载链接 百度网盘下载链接(提取码:1314)
源代码:
import os
import base64
import tkinter as tk
import tkinter.filedialog as tf
import tkinter.messagebox as tm
import random
import warnings
from win32api import SetFileAttributes
from win32con import FILE_ATTRIBUTE_HIDDEN
from subprocess import Popen, PIPE, STDOUT
from sys import executable as EXECUTABLE
import Tool.MyLogger as ml
from ctypes import windll
class PackTool:
__filePath = ""
__execPath = ""
__invokeName = ""
__pyName = ""
__tips = ('请选择ICO文件并输入ICO调用变量名称和生成的py文件名称', '生成的py文件与ICO文件位于同一目录下', '可以使用数据管理来清空py文件中的内容', '若ICO调用变量名称已存在会提示你是否覆盖')
__site = [('数据打包', 0), ('数据管理', 1), ('数据解码', 2)]
__log = ml.IMyLogger()
__endFlag = False # IcoBase64Decoder()执行完成的标志
__callNum = 5 # IcoSetToTk()递归调用的次数
__isDirect = False # 是否直接调用API
# 创建快捷方式
def _createShortcut(self):
v_execpath = os.path.dirname(EXECUTABLE)
self.__execPath = v_execpath.replace('\\', '/')
v_batpath = v_execpath + "\\init.bat"
if os.path.exists(v_batpath):
self.__log.InLog("Create shortcut successful!")
startcmd = "start " + "shortcut.bat"
startpath = v_execpath + "\\shortcut.bat"
cmd = "cd " + v_execpath + " && " + startcmd
pop = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT)
pop.wait()
if pop.returncode == 0:
pop.terminate()
pop.kill()
SetFileAttributes(startpath, FILE_ATTRIBUTE_HIDDEN)
def _windowSize(self, p_width, p_height):
size = '%dx%d+%d+%d' % (
p_width, p_height, (self.__screenWidth - p_width) / 2, (self.__screenHeight - p_height) / 2)
return size
def __init__(self, p_activeTk=True):
# 打包时请取消以下这句代码的注释
self._createShortcut()
# 告诉操作系统使用程序自身的dpi适配
windll.shcore.SetProcessDpiAwareness(1)
# 获取屏幕的缩放因子
ScaleFactor = windll.shcore.GetScaleFactorForDevice(0)
self.__root = tk.Tk()
self.__root.protocol("WM_DELETE_WINDOW", func=lambda: self._ThreadClose())
self.__root.title('打包工具')
# 获取屏幕宽高
self.__screenWidth = self.__root.winfo_screenwidth()
self.__screenHeight = self.__root.winfo_screenheight()
# 设置窗口大小
self.__root.geometry(self._windowSize(int(self.__screenWidth * 0.35), int(self.__screenHeight * 0.625)))
# 设置程序缩放
self.__root.tk.call('tk', 'scaling', ScaleFactor / 75)
self.__v = tk.IntVar()
self.__Fr_m = tk.Frame(self.__root)
self.__Fr_m.pack()
self.__Fr_subm1 = tk.Frame(self.__Fr_m)
for name, index in self.__site:
Rb = tk.Radiobutton(self.__Fr_subm1, text=name, variable=self.__v, value=index,
command=lambda: self._ChangeMode())
Rb.pack(side='left', padx='5px', pady='5px')
self.__Fr_subm1.pack()
self.__Fr_subm2 = tk.Frame(self.__Fr_m)
self.__Lb_1 = tk.Label(self.__Fr_subm2, text='Tip:请选择ICO文件并输入ICO调用变量名称和生成的py文件名称', fg='blue')
self.__Lb_1.pack(pady='5px')
self.__Fr_subm2.pack(pady='5px')
self.__Fr_sub1 = tk.Frame(self.__Fr_m)
self.__Fr_sub1.pack()
self.__Fr_a1 = tk.Frame(self.__Fr_sub1)
self.__Lb_a1 = tk.Label(self.__Fr_a1, text='请选择.ico文件', fg='red')
self.__Lb_a1.pack(pady='5px')
self.__Lb_a2 = tk.Label(self.__Fr_a1, text='')
self.__Lb_a2.pack(pady='5px')
self.__Bt_a1 = tk.Button(self.__Fr_a1, text='选择文件', width=20, command=lambda: self._ChooseFile())
self.__Bt_a1.pack(pady='5px')
self.__Fr_a1.pack(pady='5px')
self.__Fr_a2 = tk.Frame(self.__Fr_sub1)
self.__Lb_a3 = tk.Label(self.__Fr_a2, text='请输入ICO调用变量名称', fg='red')
self.__Lb_a3.pack(pady='5px')
self.__Et_a1 = tk.Entry(self.__Fr_a2, exportselection=0)
self.__Et_a1.pack(pady='5px')
self.__Lb_a4 = tk.Label(self.__Fr_a2, text='请输入生成的.py文件名称', fg='red')
self.__Lb_a4.pack(pady='5px')
self.__Et_a2 = tk.Entry(self.__Fr_a2, exportselection=0)
self.__Et_a2.pack(pady='5px')
self.__Bt_a2 = tk.Button(self.__Fr_a2, text='生成ICO的py文件', width=20, command=lambda: self._IcoToBase64())
self.__Bt_a2.pack(pady='5px')
self.__Fr_a2.pack(pady='5px')
self.__Fr_sub2 = tk.Frame(self.__Fr_m)
self.__Fr_sub2.pack_forget()
self.__Fr_b1 = tk.Frame(self.__Fr_sub2)
self.__Lb_b1 = tk.Label(self.__Fr_b1, text='请选择.py文件', fg='red')
self.__Lb_b1.pack(pady='5px')
self.__Lb_b2 = tk.Label(self.__Fr_b1, text='')
self.__Lb_b2.pack(pady='5px')
self.__Bt_b1 = tk.Button(self.__Fr_b1, text='选择文件', width=20, command=lambda: self._ChooseFile())
self.__Bt_b1.pack(pady='5px')
self.__Fr_b1.pack(pady='5px')
self.__Fr_b2 = tk.Frame(self.__Fr_sub2)
self.__Bt_b2 = tk.Button(self.__Fr_b2, text='清空py文件内容', width=20, command=lambda: self._ClearPyContent())
self.__Bt_b2.pack(pady='5px')
self.__Fr_b2.pack(pady='5px')
self.__Fr_sub3 = tk.Frame(self.__Fr_m)
self.__Fr_sub3.pack()
self.__Fr_c1 = tk.Frame(self.__Fr_sub3)
self.__Lb_c1 = tk.Label(self.__Fr_c1, text='请选择.py文件', fg='red')
self.__Lb_c1.pack(pady='5px')
self.__Lb_c2 = tk.Label(self.__Fr_c1, text='')
self.__Lb_c2.pack(pady='5px')
self.__Bt_c1 = tk.Button(self.__Fr_c1, text='选择文件', width=20, command=lambda: self._ChooseFile())
self.__Bt_c1.pack(pady='5px')
self.__Fr_c1.pack(pady='5px')
self.__Fr_c2 = tk.Frame(self.__Fr_sub3)
self.__Lb_c3 = tk.Label(self.__Fr_c2, text='请输入ICO调用变量名称', fg='red')
self.__Lb_c3.pack(pady='5px')
self.__Et_c1 = tk.Entry(self.__Fr_c2, exportselection=0)
self.__Et_c1.pack(pady='5px')
self.__Lb_c4 = tk.Label(self.__Fr_c2, text='请输入生成的.ico文件名称', fg='red')
self.__Lb_c4.pack(pady='5px')
self.__Et_c2 = tk.Entry(self.__Fr_c2, exportselection=0)
self.__Et_c2.pack(pady='5px')
self.__Bt_c2 = tk.Button(self.__Fr_c2, text='数据解压', width=20,
command=lambda: self._DataDecoder())
self.__Bt_c2.pack(pady='5px')
self.__Fr_c2.pack(pady='5px')
self.IcoBase64Encoder(self.__execPath + '/image/包1.ico', 'pak1', 'ico.py',
self.__execPath + '/build')
self.IcoBase64Decoder('packTool', p_icoVariableName='pak1',
p_pyPath=self.__execPath + '/build/ico.py')
self.IcoSetToTk(self.__root, 'packTool', self.__execPath + '/build')
if p_activeTk is True:
self.__isDirect = False
self.__root.mainloop()
# ICO文件转二进制内容,并保存为py文件
def _IcoToBase64(self, p_icoPath=None, p_icoVariableName=None, p_pyName=None, p_pyFolderPath=None):
v_isCheck = True
if self.__isDirect is False:
v_isCheck = self._InputCheckAndDeal()
if v_isCheck:
v_path = self.__filePath
v_invokeName = self.__invokeName
v_pyName = self.__pyName
v_pyPath = v_pyName + ".py"
if self.__isDirect is True:
v_path = p_icoPath
v_invokeName = p_icoVariableName
v_pyName = p_pyName
v_pyPath = p_pyFolderPath + '/' + v_pyName + '.py'
if os.path.exists(v_path):
# 打开ico文件并转换为二进制
open_icon = open(v_path, "rb")
b64str = base64.b64encode(open_icon.read())
open_icon.close()
# 将内容写入py文件中
v_content = " = %s" % b64str
write_data = v_invokeName + v_content + "\n"
v_repeateValue = self._IsIcoRepeate(v_pyPath, p_invokeName=v_invokeName, p_content=v_content)
if v_repeateValue is True:
f = open(v_pyPath, "a+")
f.write(write_data)
f.close()
if self.__isDirect is False:
self.__Lb_1.config(text='生成' + v_pyName + '.py文件成功!')
# 对生成的py文件内容进行查重
v_data = self._IcoRepeateCheck(v_pyPath)
v_dataStr = ""
for d in v_data:
v_dataStr += d
if v_dataStr != "":
f = open(v_pyPath, "w+")
f.write(v_dataStr)
f.close()
else:
if self.__isDirect is True:
warnings.warn('根据文件路径所查找的文件不存在!', stacklevel=4)
else:
tm.showwarning('Warning', '根据文件路径所查找的文件不存在!')
self._RandomTip()
# ICO的py文件二进制解码器,将生成一个指定名称的ico文件
def IcoBase64Encoder(self, p_icoPath, p_icoVariableName, p_pyName, p_pyFolderPath):
v_pyName = p_pyName.replace('.py', '')
self.__isDirect = True
self._IcoToBase64(p_icoPath, p_icoVariableName, v_pyName, p_pyFolderPath)
# ICO的py文件二进制解码器,将生成一个指定名称的ico文件
def IcoBase64Decoder(self, p_icoName, p_icoBytesVariable=None, p_icoVariableName=None, p_pyPath=None):
self.__isDirect = True
self._Base64ToIco(p_icoName, p_icoBytesVariable, p_icoVariableName, p_pyPath)
def _Base64ToIco(self, p_icoName, p_icoBytesVariable=None, p_icoVariableName=None, p_pyPath=None):
v_icoPath = p_pyPath.replace(p_pyPath.split('/')[-1], '')
v_icoPath = v_icoPath + p_icoName.replace('.ico', '') + ".ico"
picture = open(v_icoPath, "wb+")
if p_icoBytesVariable is not None:
picture.write(base64.b64decode(p_icoBytesVariable))
elif p_icoVariableName is not None and p_pyPath is not None:
v_content = self._GetContentByName(p_icoVariableName, p_pyPath)
if v_content is None:
warnings.warn('对应的py文件中不存在ICO调用变量名称:' + p_icoVariableName, stacklevel=3)
return
else:
v_icoVariable = v_content[2:len(v_content) - 2]
picture.write(base64.b64decode(v_icoVariable))
picture.flush()
os.fsync(picture.fileno())
picture.close()
self.__endFlag = True
# 给将要打包的Tk()设置Ico
def IcoSetToTk(self, p_tk, p_icoName, p_pyFolderPath):
self.__isDirect = True
self._IcoSet(p_tk, p_icoName, p_pyFolderPath)
def _IcoSet(self, p_tk, p_icoName, p_pyFolderPath):
self.__callNum -= 1
if self.__endFlag:
# 获得生成的ico文件的绝对路径
v_icoName = p_icoName.replace('.ico', '') + '.ico'
v_path = p_pyFolderPath + '/' + v_icoName
if p_pyFolderPath[-1] == '/':
v_path = p_pyFolderPath + v_icoName
# 判断路径下ico文件是否存在
if os.path.exists(v_path):
# 对路径进行转码和解码操作
v_path = v_path.encode("utf-8")
v_path = v_path.decode("utf-8", "strict")
# 设置Tk()的窗口图标,报错的就是下面这一行
p_tk.iconbitmap(default=v_path)
os.remove(v_path)
else:
if self.__isDirect is True:
warnings.warn('二进制解码生成的ico文件不存在!', stacklevel=3)
# 录入日志信息
self.__log.InLog("Warning:二进制解码生成的ico文件不存在!")
else:
tm.showwarning('Warning', '二进制解码生成的ico文件不存在!')
else:
if self.__callNum > 0:
self._IcoSet(p_tk, p_icoName, p_pyFolderPath)
else:
self.__log.InLog("IcoSetToTk递归结束")
self.__endFlag = False
self.__callNum = 5
# 针对ImgToBase64方法中的查重检测,返回是否重复的信息
def _IsIcoRepeate(self, p_path, **p_Names):
f = open(p_path, 'a')
f.close()
f = open(p_path, 'r')
v_invokeName = p_Names['p_invokeName']
v_contents = f.readlines()
self._DealValueOfList(v_contents, '\n')
list_names = list()
list_contents = list()
for c in v_contents:
s1 = c.replace(' ', '')
s = s1.split('=', 1)
list_names.append(s[0])
list_contents.append(s[1])
if list_names.count(v_invokeName) > 0:
if self.__isDirect is False:
isRight = tm.askyesno('Warning', '输入的ICO调用变量名称已存在,是否覆盖?')
return isRight
return True
# 针对ImgToBase64方法中的查重检测,返回去重后的数据列表
def _IcoRepeateCheck(self, p_path):
f = open(p_path, 'r')
v_contents = f.readlines()
self._DealValueOfList(v_contents, '\n')
list_names = list()
list_contents = list()
list_nc = list()
for c in v_contents:
s = c.replace(' ', '')
s = s.split('=', 1)
list_names.append(s[0])
list_contents.append(s[1])
list_indexN = self._DealRepeateValueOfList(list_names)
list_indexC = self._DealRepeateValueOfList(list_contents)
# 名称集和数据集重复元素的索引求并集
for i in list_indexN:
if i not in list_indexC:
list_indexC.append(i)
for i in list_indexC:
if i not in list_indexN:
list_indexN.append(i)
# 对重复元素进行删除
for i in list_indexN:
del list_names[i]
for i in list_indexC:
del list_contents[i]
# 重新对数据进行拼接
if len(list_names) > 0 and len(list_contents) > 0:
for i in range(0, len(list_names)):
list_nc.append(list_names[i] + " = " + list_contents[i])
return list_nc
# 用于检测列表中的重复数据,返回重复数据的索引列表
def _DealRepeateValueOfList(self, p_list):
list_index = list()
for i in range(0, len(p_list)):
for j in range(i + 1, len(p_list)):
if p_list[i] == p_list[j]:
list_index.append(i)
break
return list_index
# 用于剔除列表中的指定数据
def _DealValueOfList(self, p_list, *p_data):
for d in p_data:
while p_list.count(d) > 0:
p_list.remove(d)
# 用于开启选择文件窗口
def _ChooseFile(self):
self._RandomTip()
self.__filePath = tf.askopenfilename()
if self.__v.get() == 0:
self.__Lb_a2.config(text=self.__filePath)
elif self.__v.get() == 1:
self.__Lb_b2.config(text=self.__filePath)
elif self.__v.get() == 2:
self.__Lb_c2.config(text=self.__filePath)
# 用于清空上功能模块中共用的内容
def _ClearShareContent(self):
self.__filePath = ''
self._RandomTip()
# 用于在关闭窗口时关闭该应用线程
def _ThreadClose(self):
self.__root.quit()
self.__root.destroy()
# 用于清除py文件中的内容
def _ClearPyContent(self):
v_path = self.__filePath
v_pyName = self.__pyName
if os.path.exists(v_path):
f = open(v_path, 'w')
f.write('')
f.close()
self.__Lb_1.config(text='清空' + v_pyName + '.py' + '文件成功')
else:
tm.showwarning('Warning', '请选择待清空的.py文件')
# 根据ico调用变量名称获取其二进制内容
def _GetContentByName(self, p_icoVariableName, p_path):
if os.path.exists(p_path):
f = open(p_path, 'r')
v_contents = f.readlines()
self._DealValueOfList(v_contents, '\n')
list_names = list()
list_contents = list()
for c in v_contents:
s = c.replace(' ', '')
s = s.split('=', 1)
list_names.append(s[0])
list_contents.append(s[1])
f.close()
for i in range(0, len(list_contents)):
if list_names[i] == p_icoVariableName:
return list_contents[i]
else:
warnings.warn('路径' + p_path + '所对应的py文件不存在!', stacklevel=4)
return None
# 数据解压
def _DataDecoder(self):
v_icoName = self.__Et_c2.get()
v_icoVariableName = self.__Et_c1.get()
v_path = self.__filePath
v_content = self._GetContentByName(v_icoVariableName, v_path)
if v_content is None:
tm.showwarning('Warning', '所选择的py文件中不存在ICO调用变量名称:' + v_icoVariableName)
else:
v_icoVariable = v_content[2:len(v_content) - 2]
self._Base64ToIco(v_icoName, v_icoVariable)
self.__Lb_1.config(text='数据解压成功!')
# 用于更换功能模块
def _ChangeMode(self):
self._RandomTip()
self._ClearShareContent()
if self.__v.get() == 0:
self.__Fr_sub1.pack()
self.__Fr_sub2.pack_forget()
self.__Fr_sub3.pack_forget()
elif self.__v.get() == 1:
self.__Fr_sub1.pack_forget()
self.__Fr_sub3.pack_forget()
self.__Fr_sub2.pack()
elif self.__v.get() == 2:
self.__Fr_sub1.pack_forget()
self.__Fr_sub2.pack_forget()
self.__Fr_sub3.pack()
# 字符串中是否包括中文(编码集UTF-8),包括则返回True,反之则返回False
def _IsIncludeChinese(self, p_str):
for s in p_str:
if u'\u4e00' <= s <= u'\u9fa5':
return True
return False
# 输入检测和处理
def _InputCheckAndDeal(self):
self.__invokeName = str(self.__Et_a1.get()).strip()
self.__pyName = str(self.__Et_a2.get()).strip()
v_path = self.__filePath
v_invokeName = self.__invokeName
v_pyName = self.__pyName
if v_path == "":
tm.showwarning('Warning', '请选择文件路径!')
self._RandomTip()
return False
if v_path.split('/')[-1].split('.')[-1] != 'ico':
tm.showwarning('Warning', '请选择.ico文件!')
self._RandomTip()
return False
if v_invokeName == "":
tm.showwarning('Warning', '输入的ICO调用变量名称为空!')
self._RandomTip()
return False
if v_invokeName.isalnum() is False or self._IsIncludeChinese(
v_invokeName) is True or v_invokeName.isnumeric() is True:
tm.showwarning('Warning', '输入的ICO调用变量名称必须满足以下条件:\n1.不包括除字母或数字外的其它字符\n2.不能为纯数字')
self._RandomTip()
return False
if v_pyName == "":
tm.showwarning('Warning', '输入的生成的.py文件名称为空!')
self._RandomTip()
return False
if v_pyName.isalnum() is False or self._IsIncludeChinese(v_pyName) is True:
tm.showwarning('Warning', '输入的生成的.py文件名称不能包括除字母或数字外的其它字符!')
self._RandomTip()
return False
elif '.py' in v_pyName:
self.__pyName = v_pyName.replace('.py', '')
return True
def _RandomTip(self):
i = random.randint(0, len(self.__tips) - 1)
self.__Lb_1.config(text='Tip:' + self.__tips[i])
class IPackTool:
def __init__(self, p_activeTk=True):
self.__pk = PackTool(p_activeTk)
def IcoBase64Decoder(self, p_icoName, p_icoBytesVariable=None, p_icoVariableName=None, p_pyPath=None):
self.__pk.IcoBase64Decoder(p_icoName, p_icoBytesVariable, p_icoVariableName, p_pyPath)
def IcoSetToTk(self, p_tk, p_icoName, p_pyFolderPath):
self.__pk.IcoSetToTk(p_tk, p_icoName, p_pyFolderPath)
def IcoBase64Encoder(self, p_icoPath, p_icoVariableName, p_pyName, p_pyFolderPath):
self.__pk.IcoBase64Encoder(p_icoPath, p_icoVariableName, p_pyName, p_pyFolderPath)
# 打包后请取消此行代码的注释
mIPT = IPackTool()
如果这篇文章对你有帮助,请给作者点个赞吧!