assets.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. #!/usr/bin/env python3
  2. import logging
  3. import argparse
  4. import subprocess
  5. import io
  6. import os
  7. import sys
  8. ICONS_SUPPORTED_FORMATS = ["png"]
  9. ICONS_TEMPLATE_H_HEADER = """#pragma once
  10. #include <gui/icon.h>
  11. """
  12. ICONS_TEMPLATE_H_ICON_NAME = "extern const Icon {name};\n"
  13. ICONS_TEMPLATE_C_HEADER = """#include \"assets_icons.h\"
  14. #include <gui/icon_i.h>
  15. """
  16. ICONS_TEMPLATE_C_FRAME = "const uint8_t {name}[] = {data};\n"
  17. ICONS_TEMPLATE_C_DATA = "const uint8_t *{name}[] = {data};\n"
  18. ICONS_TEMPLATE_C_ICONS = "const Icon {name} = {{.width={width},.height={height},.frame_count={frame_count},.frame_rate={frame_rate},.frames=_{name}}};\n"
  19. class Main:
  20. def __init__(self):
  21. # command args
  22. self.parser = argparse.ArgumentParser()
  23. self.parser.add_argument("-d", "--debug", action="store_true", help="Debug")
  24. self.subparsers = self.parser.add_subparsers(help="sub-command help")
  25. self.parser_icons = self.subparsers.add_parser(
  26. "icons", help="Process icons and build icon registry"
  27. )
  28. self.parser_icons.add_argument("source_directory", help="Source directory")
  29. self.parser_icons.add_argument("output_directory", help="Output directory")
  30. self.parser_icons.set_defaults(func=self.icons)
  31. self.parser_manifest = self.subparsers.add_parser(
  32. "manifest", help="Create directory Manifest"
  33. )
  34. self.parser_manifest.add_argument("local_path", help="local_path")
  35. self.parser_manifest.set_defaults(func=self.manifest)
  36. self.parser_copro = self.subparsers.add_parser(
  37. "copro", help="Gather copro binaries for packaging"
  38. )
  39. self.parser_copro.add_argument("cube_dir", help="Path to Cube folder")
  40. self.parser_copro.add_argument("output_dir", help="Path to output folder")
  41. self.parser_copro.add_argument("mcu", help="MCU series as in copro folder")
  42. self.parser_copro.set_defaults(func=self.copro)
  43. # logging
  44. self.logger = logging.getLogger()
  45. def __call__(self):
  46. self.args = self.parser.parse_args()
  47. if "func" not in self.args:
  48. self.parser.error("Choose something to do")
  49. # configure log output
  50. self.log_level = logging.DEBUG if self.args.debug else logging.INFO
  51. self.logger.setLevel(self.log_level)
  52. self.handler = logging.StreamHandler(sys.stdout)
  53. self.handler.setLevel(self.log_level)
  54. self.formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
  55. self.handler.setFormatter(self.formatter)
  56. self.logger.addHandler(self.handler)
  57. # execute requested function
  58. self.args.func()
  59. def icons(self):
  60. self.logger.debug(f"Converting icons")
  61. icons_c = open(os.path.join(self.args.output_directory, "assets_icons.c"), "w")
  62. icons_c.write(ICONS_TEMPLATE_C_HEADER)
  63. icons = []
  64. # Traverse icons tree, append image data to source file
  65. for dirpath, dirnames, filenames in os.walk(self.args.source_directory):
  66. self.logger.debug(f"Processing directory {dirpath}")
  67. dirnames.sort()
  68. if not filenames:
  69. continue
  70. if "frame_rate" in filenames:
  71. self.logger.debug(f"Folder contatins animation")
  72. icon_name = "A_" + os.path.split(dirpath)[1].replace("-", "_")
  73. width = height = None
  74. frame_count = 0
  75. frame_rate = 0
  76. frame_names = []
  77. for filename in sorted(filenames):
  78. fullfilename = os.path.join(dirpath, filename)
  79. if filename == "frame_rate":
  80. frame_rate = int(open(fullfilename, "r").read().strip())
  81. continue
  82. elif not self.iconIsSupported(filename):
  83. continue
  84. self.logger.debug(f"Processing animation frame {filename}")
  85. temp_width, temp_height, data = self.icon2header(fullfilename)
  86. if width is None:
  87. width = temp_width
  88. if height is None:
  89. height = temp_height
  90. assert width == temp_width
  91. assert height == temp_height
  92. frame_name = f"_{icon_name}_{frame_count}"
  93. frame_names.append(frame_name)
  94. icons_c.write(
  95. ICONS_TEMPLATE_C_FRAME.format(name=frame_name, data=data)
  96. )
  97. frame_count += 1
  98. assert frame_rate > 0
  99. assert frame_count > 0
  100. icons_c.write(
  101. ICONS_TEMPLATE_C_DATA.format(
  102. name=f"_{icon_name}", data=f'{{{",".join(frame_names)}}}'
  103. )
  104. )
  105. icons_c.write("\n")
  106. icons.append((icon_name, width, height, frame_rate, frame_count))
  107. else:
  108. # process icons
  109. for filename in filenames:
  110. if not self.iconIsSupported(filename):
  111. continue
  112. self.logger.debug(f"Processing icon {filename}")
  113. icon_name = "I_" + "_".join(filename.split(".")[:-1]).replace(
  114. "-", "_"
  115. )
  116. fullfilename = os.path.join(dirpath, filename)
  117. width, height, data = self.icon2header(fullfilename)
  118. frame_name = f"_{icon_name}_0"
  119. icons_c.write(
  120. ICONS_TEMPLATE_C_FRAME.format(name=frame_name, data=data)
  121. )
  122. icons_c.write(
  123. ICONS_TEMPLATE_C_DATA.format(
  124. name=f"_{icon_name}", data=f"{{{frame_name}}}"
  125. )
  126. )
  127. icons_c.write("\n")
  128. icons.append((icon_name, width, height, 0, 1))
  129. # Create array of images:
  130. self.logger.debug(f"Finalizing source file")
  131. for name, width, height, frame_rate, frame_count in icons:
  132. icons_c.write(
  133. ICONS_TEMPLATE_C_ICONS.format(
  134. name=name,
  135. width=width,
  136. height=height,
  137. frame_rate=frame_rate,
  138. frame_count=frame_count,
  139. )
  140. )
  141. icons_c.write("\n")
  142. # Create Public Header
  143. self.logger.debug(f"Creating header")
  144. icons_h = open(os.path.join(self.args.output_directory, "assets_icons.h"), "w")
  145. icons_h.write(ICONS_TEMPLATE_H_HEADER)
  146. for name, width, height, frame_rate, frame_count in icons:
  147. icons_h.write(ICONS_TEMPLATE_H_ICON_NAME.format(name=name))
  148. self.logger.debug(f"Done")
  149. def icon2header(self, file):
  150. output = subprocess.check_output(["convert", file, "xbm:-"])
  151. assert output
  152. f = io.StringIO(output.decode().strip())
  153. width = int(f.readline().strip().split(" ")[2])
  154. height = int(f.readline().strip().split(" ")[2])
  155. data = f.read().strip().replace("\n", "").replace(" ", "").split("=")[1][:-1]
  156. return width, height, data
  157. def iconIsSupported(self, filename):
  158. extension = filename.lower().split(".")[-1]
  159. return extension in ICONS_SUPPORTED_FORMATS
  160. def manifest(self):
  161. from flipper.manifest import Manifest
  162. directory_path = os.path.normpath(self.args.local_path)
  163. if not os.path.isdir(directory_path):
  164. self.logger.error(f'"{directory_path}" is not a directory')
  165. exit(255)
  166. manifest_file = os.path.join(directory_path, "Manifest")
  167. old_manifest = Manifest()
  168. if os.path.exists(manifest_file):
  169. self.logger.info(
  170. f"old manifest is present, loading for compare and removing file"
  171. )
  172. old_manifest.load(manifest_file)
  173. os.unlink(manifest_file)
  174. self.logger.info(f'Creating new Manifest for directory "{directory_path}"')
  175. new_manifest = Manifest()
  176. new_manifest.create(directory_path)
  177. new_manifest.save(manifest_file)
  178. self.logger.info(f"Comparing new manifest with old")
  179. only_in_old, changed, only_in_new = Manifest.compare(old_manifest, new_manifest)
  180. for record in only_in_old:
  181. self.logger.info(f"Only in old: {record}")
  182. for record in changed:
  183. self.logger.info(f"Changed: {record}")
  184. for record in only_in_new:
  185. self.logger.info(f"Only in new: {record}")
  186. self.logger.info(f"Complete")
  187. def copro(self):
  188. from flipper.copro import Copro
  189. copro = Copro(self.args.mcu)
  190. copro.loadCubeInfo(self.args.cube_dir)
  191. copro.bundle(self.args.output_dir)
  192. if __name__ == "__main__":
  193. Main()()