assets.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. #!/usr/bin/env python3
  2. import logging
  3. import argparse
  4. import subprocess
  5. import io
  6. import os
  7. import sys
  8. import re
  9. import struct
  10. import datetime
  11. ICONS_SUPPORTED_FORMATS = ["png"]
  12. ICONS_TEMPLATE_H_HEADER = """#pragma once
  13. #include <gui/icon.h>
  14. typedef enum {
  15. """
  16. ICONS_TEMPLATE_H_ICON_NAME = "\t{name},\n"
  17. ICONS_TEMPLATE_H_FOOTER = """} IconName;
  18. Icon * assets_icons_get(IconName name);
  19. """
  20. ICONS_TEMPLATE_H_I = """#pragma once
  21. #include <assets_icons.h>
  22. const IconData * assets_icons_get_data(IconName name);
  23. """
  24. ICONS_TEMPLATE_C_HEADER = """#include \"assets_icons_i.h\"
  25. #include <gui/icon_i.h>
  26. """
  27. ICONS_TEMPLATE_C_FRAME = "const uint8_t {name}[] = {data};\n"
  28. ICONS_TEMPLATE_C_DATA = "const uint8_t *{name}[] = {data};\n"
  29. ICONS_TEMPLATE_C_ICONS_ARRAY_START = "const IconData icons[] = {\n"
  30. ICONS_TEMPLATE_C_ICONS_ITEM = "\t{{ .width={width}, .height={height}, .frame_count={frame_count}, .frame_rate={frame_rate}, .frames=_{name} }},\n"
  31. ICONS_TEMPLATE_C_ICONS_ARRAY_END = "};"
  32. ICONS_TEMPLATE_C_FOOTER = """
  33. const IconData * assets_icons_get_data(IconName name) {
  34. return &icons[name];
  35. }
  36. Icon * assets_icons_get(IconName name) {
  37. return icon_alloc(assets_icons_get_data(name));
  38. }
  39. """
  40. class Assets:
  41. def __init__(self):
  42. # command args
  43. self.parser = argparse.ArgumentParser()
  44. self.parser.add_argument("-d", "--debug", action="store_true", help="Debug")
  45. self.subparsers = self.parser.add_subparsers(help="sub-command help")
  46. self.parser_icons = self.subparsers.add_parser(
  47. "icons", help="Process icons and build icon registry"
  48. )
  49. self.parser_icons.add_argument(
  50. "-s", "--source-directory", help="Source directory"
  51. )
  52. self.parser_icons.add_argument(
  53. "-o", "--output-directory", help="Output directory"
  54. )
  55. self.parser_icons.set_defaults(func=self.icons)
  56. self.parser_otp = self.subparsers.add_parser(
  57. "otp", help="OTP HW version generator"
  58. )
  59. self.parser_otp.add_argument(
  60. "--version", type=int, help="Version", required=True
  61. )
  62. self.parser_otp.add_argument(
  63. "--firmware", type=int, help="Firmware", required=True
  64. )
  65. self.parser_otp.add_argument("--body", type=int, help="Body", required=True)
  66. self.parser_otp.add_argument(
  67. "--connect", type=int, help="Connect", required=True
  68. )
  69. self.parser_otp.add_argument("--name", type=str, help="Name", required=True)
  70. self.parser_otp.add_argument("file", help="Output file")
  71. self.parser_otp.set_defaults(func=self.otp)
  72. # logging
  73. self.logger = logging.getLogger()
  74. def __call__(self):
  75. self.args = self.parser.parse_args()
  76. if "func" not in self.args:
  77. self.parser.error("Choose something to do")
  78. # configure log output
  79. self.log_level = logging.DEBUG if self.args.debug else logging.INFO
  80. self.logger.setLevel(self.log_level)
  81. self.handler = logging.StreamHandler(sys.stdout)
  82. self.handler.setLevel(self.log_level)
  83. self.formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
  84. self.handler.setFormatter(self.formatter)
  85. self.logger.addHandler(self.handler)
  86. # execute requested function
  87. self.args.func()
  88. def otp(self):
  89. self.logger.debug(f"Generating OTP")
  90. if self.args.name:
  91. name = re.sub(
  92. "[^a-zA-Z0-9.]", "", self.args.name
  93. ) # Filter all special characters
  94. name = list(
  95. map(str, map(ord, name[0:8]))
  96. ) # Strip to 8 chars and map to ascii codes
  97. while len(name) < 8:
  98. name.append("0")
  99. n1, n2, n3, n4, n5, n6, n7, n8 = map(int, name)
  100. data = struct.pack(
  101. "<BBBBLBBBBBBBB",
  102. self.args.version,
  103. self.args.firmware,
  104. self.args.body,
  105. self.args.connect,
  106. int(datetime.datetime.now().timestamp()),
  107. n1,
  108. n2,
  109. n3,
  110. n4,
  111. n5,
  112. n6,
  113. n7,
  114. n8,
  115. )
  116. open(self.args.file, "wb").write(data)
  117. def icons(self):
  118. self.logger.debug(f"Converting icons")
  119. icons_c = open(os.path.join(self.args.output_directory, "assets_icons.c"), "w")
  120. icons_c.write(ICONS_TEMPLATE_C_HEADER)
  121. icons = []
  122. # Traverse icons tree, append image data to source file
  123. for dirpath, dirnames, filenames in os.walk(self.args.source_directory):
  124. self.logger.debug(f"Processing directory {dirpath}")
  125. if not filenames:
  126. continue
  127. if "frame_rate" in filenames:
  128. self.logger.debug(f"Folder contatins animation")
  129. icon_name = "A_" + os.path.split(dirpath)[1].replace("-", "_")
  130. width = height = None
  131. frame_count = 0
  132. frame_rate = 0
  133. frame_names = []
  134. for filename in sorted(filenames):
  135. fullfilename = os.path.join(dirpath, filename)
  136. if filename == "frame_rate":
  137. frame_rate = int(open(fullfilename, "r").read().strip())
  138. continue
  139. elif not self.iconIsSupported(filename):
  140. continue
  141. self.logger.debug(f"Processing animation frame {filename}")
  142. temp_width, temp_height, data = self.icon2header(fullfilename)
  143. if width is None:
  144. width = temp_width
  145. if height is None:
  146. height = temp_height
  147. assert width == temp_width
  148. assert height == temp_height
  149. frame_name = f"_{icon_name}_{frame_count}"
  150. frame_names.append(frame_name)
  151. icons_c.write(
  152. ICONS_TEMPLATE_C_FRAME.format(name=frame_name, data=data)
  153. )
  154. frame_count += 1
  155. assert frame_rate > 0
  156. assert frame_count > 0
  157. icons_c.write(
  158. ICONS_TEMPLATE_C_DATA.format(
  159. name=f"_{icon_name}", data=f'{{{",".join(frame_names)}}}'
  160. )
  161. )
  162. icons_c.write("\n")
  163. icons.append((icon_name, width, height, frame_rate, frame_count))
  164. else:
  165. # process icons
  166. for filename in filenames:
  167. if not self.iconIsSupported(filename):
  168. continue
  169. self.logger.debug(f"Processing icon {filename}")
  170. icon_name = "I_" + "_".join(filename.split(".")[:-1]).replace(
  171. "-", "_"
  172. )
  173. fullfilename = os.path.join(dirpath, filename)
  174. width, height, data = self.icon2header(fullfilename)
  175. frame_name = f"_{icon_name}_0"
  176. icons_c.write(
  177. ICONS_TEMPLATE_C_FRAME.format(name=frame_name, data=data)
  178. )
  179. icons_c.write(
  180. ICONS_TEMPLATE_C_DATA.format(
  181. name=f"_{icon_name}", data=f"{{{frame_name}}}"
  182. )
  183. )
  184. icons_c.write("\n")
  185. icons.append((icon_name, width, height, 0, 1))
  186. # Create array of images:
  187. self.logger.debug(f"Finalizing source file")
  188. icons_c.write(ICONS_TEMPLATE_C_ICONS_ARRAY_START)
  189. for name, width, height, frame_rate, frame_count in icons:
  190. icons_c.write(
  191. ICONS_TEMPLATE_C_ICONS_ITEM.format(
  192. name=name,
  193. width=width,
  194. height=height,
  195. frame_rate=frame_rate,
  196. frame_count=frame_count,
  197. )
  198. )
  199. icons_c.write(ICONS_TEMPLATE_C_ICONS_ARRAY_END)
  200. icons_c.write(ICONS_TEMPLATE_C_FOOTER)
  201. icons_c.write("\n")
  202. # Create Public Header
  203. self.logger.debug(f"Creating header")
  204. icons_h = open(os.path.join(self.args.output_directory, "assets_icons.h"), "w")
  205. icons_h.write(ICONS_TEMPLATE_H_HEADER)
  206. for name, width, height, frame_rate, frame_count in icons:
  207. icons_h.write(ICONS_TEMPLATE_H_ICON_NAME.format(name=name))
  208. icons_h.write(ICONS_TEMPLATE_H_FOOTER)
  209. # Create Private Header
  210. icons_h_i = open(
  211. os.path.join(self.args.output_directory, "assets_icons_i.h"), "w"
  212. )
  213. icons_h_i.write(ICONS_TEMPLATE_H_I)
  214. self.logger.debug(f"Done")
  215. def icon2header(self, file):
  216. output = subprocess.check_output(["convert", file, "xbm:-"])
  217. assert output
  218. f = io.StringIO(output.decode().strip())
  219. width = int(f.readline().strip().split(" ")[2])
  220. height = int(f.readline().strip().split(" ")[2])
  221. data = f.read().strip().replace("\n", "").replace(" ", "").split("=")[1][:-1]
  222. return width, height, data
  223. def iconIsSupported(self, filename):
  224. extension = filename.lower().split(".")[-1]
  225. return extension in ICONS_SUPPORTED_FORMATS
  226. if __name__ == "__main__":
  227. Assets()()