utils.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. # =======================================
  2. # =============== 通用工具 ===============
  3. # =======================================
  4. import re
  5. import os
  6. from PySide2.QtGui import QClipboard
  7. from PySide2.QtCore import QFileInfo
  8. from PySide2.QtQml import QJSValue
  9. from urllib.parse import unquote # 路径解码
  10. from umi_log import logger
  11. Clipboard = QClipboard() # 剪贴板
  12. ImageSuf = [ # 合法图片后缀
  13. ".jpg",
  14. ".jpe",
  15. ".jpeg",
  16. ".jfif",
  17. ".png",
  18. ".webp",
  19. ".bmp",
  20. ".tif",
  21. ".tiff",
  22. ]
  23. DocSuf = [ # 合法文档后缀
  24. ".pdf",
  25. ".xps",
  26. ".epub",
  27. ".mobi",
  28. ".fb2",
  29. ".cbz",
  30. ]
  31. # 传入文件名,检测是否含非法字符。没问题返回True
  32. def allowedFileName(fn):
  33. pattern = r'[\\/:*?"<>|]'
  34. if re.search(pattern, fn):
  35. return False # 转布尔值
  36. else:
  37. return True
  38. # 路径是图片返回true
  39. def isImg(path):
  40. return os.path.splitext(path)[-1].lower() in ImageSuf
  41. # 路径是文档返回true
  42. def isDoc(path):
  43. return os.path.splitext(path)[-1].lower() in DocSuf
  44. def _findFiles(func, paths, isRecurrence):
  45. if isinstance(paths, QJSValue):
  46. paths = paths.toVariant()
  47. if not isinstance(paths, list):
  48. logger.error(f"_findFiles 传入:{paths}, {type(paths)}")
  49. return []
  50. filePaths = []
  51. for p in paths:
  52. if os.path.isfile(p) and func(p): # 是文件,直接判断
  53. filePaths.append(os.path.abspath(p))
  54. elif os.path.isdir(p): # 是目录
  55. if isRecurrence: # 需要递归
  56. for root, dirs, files in os.walk(p):
  57. for file in files:
  58. if func(file): # 收集子文件
  59. filePaths.append(
  60. os.path.abspath(os.path.join(root, file))
  61. ) # 将路径转换为绝对路径
  62. else: # 不递归读取子文件夹
  63. for file in os.listdir(p):
  64. if os.path.isfile(os.path.join(p, file)) and func(file):
  65. filePaths.append(os.path.abspath(os.path.join(p, file)))
  66. for i, p in enumerate(filePaths): # 规范化正斜杠
  67. filePaths[i] = p.replace("\\", "/")
  68. return filePaths
  69. # 传入路径列表,在路径中搜索图片。isRecurrence=True时递归搜索。
  70. def findImages(paths, isRecurrence):
  71. return _findFiles(isImg, paths, isRecurrence)
  72. # 传 入路径列表,在路径中搜索文档。isRecurrence=True时递归搜索。
  73. def findDocs(paths, isRecurrence):
  74. return _findFiles(isDoc, paths, isRecurrence)
  75. # 复制文本到剪贴板
  76. def copyText(text):
  77. Clipboard.setText(text)
  78. # QUrl列表 转 String列表
  79. def QUrl2String(urls):
  80. resList = []
  81. for url in urls:
  82. if url.isLocalFile():
  83. u = unquote(url.toLocalFile()) # 解码路径
  84. if QFileInfo(u).exists(): # 检查路径是否真的存在
  85. resList.append(u)
  86. return resList
  87. # 初始化配置项字典数值,等同于 Configs.qml 的 function initConfigDict
  88. # 主要是为了补充type和default
  89. def initConfigDict(dic):
  90. toDict = {}
  91. def handleConfigItem(config, key): # 处理一个配置项
  92. # 类型:指定type
  93. if not config["type"] == "":
  94. if config["type"] == "file": # 文件选择
  95. config["default"] = "" if not config["default"] is None else None
  96. elif config["type"] == "var" and config["default"] is None: # 任意类型
  97. config["default"] = ""
  98. # 类型:省略type
  99. else:
  100. if isinstance(config["default"], bool): # 布尔
  101. config["type"] = "boolean"
  102. elif "optionsList" in config: # 枚举
  103. config["type"] = "enum"
  104. if len(config["optionsList"]) == 0:
  105. logger.error(f"处理配置项异常:{key}枚举列表为空。")
  106. return
  107. if config["default"] is None:
  108. config["default"] = config["optionsList"][0][0]
  109. elif isinstance(config["default"], str): # 文本
  110. config["type"] = "text"
  111. elif isinstance(config["default"], (int, float)): # 数字
  112. config["type"] = "number"
  113. elif "btnsList" in config: # 按钮组
  114. config["type"] = "buttons"
  115. return
  116. else:
  117. logger.error(f"未知类型的配置项:{key}")
  118. return
  119. def handleConfigGroup(group, prefix=""): # 处理一个配置组
  120. for key in group:
  121. config = group[key]
  122. if not isinstance(config, dict):
  123. continue
  124. # 补充空白参数
  125. if "type" not in config: # 类型
  126. config["type"] = ""
  127. if "default" not in config: # 默认值
  128. config["default"] = None
  129. if "advanced" not in config: # 是否为高级选项
  130. config["advanced"] = False
  131. # 记录完整key
  132. fullKey = prefix + key
  133. if config["type"] == "group": # 若是配置项组,递归遍历
  134. handleConfigGroup(config, fullKey + ".") # 前缀加深一层
  135. else: # 若是配置项
  136. toDict[fullKey] = config
  137. handleConfigItem(config, fullKey)
  138. handleConfigGroup(dic)
  139. return toDict
  140. # 整理 argd 参数字典,将 float 恢复 int 类型,如 12.0 → 12
  141. def argdIntConvert(argd):
  142. for k, v in argd.items():
  143. if isinstance(v, float) and v.is_integer():
  144. argd[k] = int(v)