export.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import itertools
  2. import os
  3. import queue
  4. import time
  5. import sys
  6. import check
  7. from exception import FilterException
  8. from export_thread import ExportThread
  9. from dest_paths import format_path, make_dir_structure_for_file
  10. import image_proc
  11. import log
  12. from util import get_formats_for_license_type
  13. def export(m, filtered_emoji, input_path, formats, path, src_size,
  14. num_threads, renderer, max_batch, verbose, license_enabled, cache):
  15. """
  16. Runs the entire orxporter process, includes preliminary checking and
  17. validation of emoji metadata and running the tasks associated with exporting.
  18. """
  19. # verify emoji (in a very basic way)
  20. # --------------------------------------------------------------------------
  21. log.out('Checking emoji...', 36)
  22. check_result = check.emoji(m, filtered_emoji, input_path, formats, path, src_size,
  23. num_threads, renderer, max_batch, cache, license_enabled, verbose)
  24. exporting_emoji = check_result["exporting_emoji"]
  25. cached_emoji = check_result["cached_emoji"]
  26. cached_emoji_count = check_result["cached_emoji_count"]
  27. skipped_emoji_count = check_result["skipped_emoji_count"]
  28. # report back how the export is going to go
  29. # --------------------------------------------------------------------------
  30. if skipped_emoji_count and verbose:
  31. log.out(f"", 34) # make a new line to break it up
  32. log.out(f"Output plan:", 34)
  33. if skipped_emoji_count:
  34. log.out(f"->[skip] {skipped_emoji_count} emoji will be skipped.", 34)
  35. if not verbose:
  36. log.out(f" (use the --verbose flag to see what those emoji are and why they are being skipped.)", 34)
  37. if cached_emoji_count:
  38. log.out(f"->[cache] {cached_emoji_count} emoji will be reused from cache.", 34)
  39. if verbose:
  40. log.out(f"->[cache] {len(cached_emoji['licensed_exports'])} licensed exports.", 34)
  41. log.out(f"->[cache] {len(cached_emoji['exports'])} non-licensed exports.", 34)
  42. log.out(f"->[export] {len(exporting_emoji)} emoji will be exported.", 34)
  43. # If there's no emoji to export or copy from cache, tell the program to quit.
  44. # --------------------------------------------------------------------------
  45. if len(exporting_emoji) == 0 and cached_emoji_count == 0:
  46. raise SystemExit('>∆∆< It looks like you have no emoji to export!')
  47. # export emoji
  48. # --------------------------------------------------------------------------
  49. # declare some specs of this export.
  50. if exporting_emoji:
  51. export_step(exporting_emoji, num_threads, m, input_path, path,
  52. renderer, license_enabled, cache)
  53. # Copy files from cache
  54. # --------------------------------------------------------------------------
  55. if cached_emoji_count > 0:
  56. log.out(f"Copying {cached_emoji_count} emoji from cache...", 36)
  57. bar = log.get_progress_bar(max=cached_emoji_count)
  58. try:
  59. # Copy the exports (without license)
  60. for e, fs in cached_emoji['exports']:
  61. bar.next()
  62. for f in fs:
  63. final_path = format_path(path, e, f)
  64. make_dir_structure_for_file(final_path)
  65. cache.load_from_cache(e, f, final_path, False)
  66. # Copy the licensed exports (populated if license_enabled is True)
  67. for e, fs in cached_emoji['licensed_exports']:
  68. bar.next()
  69. for f in fs:
  70. final_path = format_path(path, e, f)
  71. make_dir_structure_for_file(final_path)
  72. cache.load_from_cache(e, f, final_path, True)
  73. except (KeyboardInterrupt, SystemExit):
  74. # Make sure the bar is properly set if oxporter is told to exit
  75. bar.finish()
  76. raise
  77. bar.finish()
  78. log.out(f"- done!", 32)
  79. # exif license pass
  80. # (currently only just applies to PNGs)
  81. # --------------------------------------------------------------------------
  82. if ('exif' in m.license) and license_enabled:
  83. exif_compatible_images = []
  84. exif_supported_formats = get_formats_for_license_type('exif')
  85. for e, fs in itertools.chain(exporting_emoji, cached_emoji['exports']):
  86. for f in fs:
  87. if f.split("-")[0] in exif_supported_formats:
  88. try:
  89. final_path = format_path(path, e, f)
  90. exif_compatible_images.append((final_path, e, f))
  91. except FilterException:
  92. if verbose:
  93. log.out(f"- Emoji filtered from metadata: {e['short']}", 34)
  94. continue
  95. if exif_compatible_images:
  96. log.out(f'Adding EXIF metadata to {len(exif_compatible_images)} compatible raster files...', 36)
  97. images_list = (i for i, _, _ in exif_compatible_images)
  98. image_proc.batch_add_exif_metadata(images_list, m.license.get('exif'), max_batch)
  99. # Copy exported emoji to cache
  100. if cache:
  101. log.out(f"Copying {len(exif_compatible_images)} licensed "
  102. "raster files to cache...", 36)
  103. for final_path, e, f in exif_compatible_images:
  104. if not cache.save_to_cache(e, f, final_path, license_enabled=True):
  105. raise RuntimeError(f"Unable to save '{e['short']}' in "
  106. f"{f} with license to cache.")
  107. def export_step(exporting_emoji, num_threads, m, input_path, path, renderer, license_enabled, cache):
  108. log.out(f"Exporting {len(exporting_emoji)} emoji...", 36)
  109. if num_threads > 1:
  110. log.out(f"-> {num_threads} threads")
  111. else:
  112. log.out(f"-> {num_threads} thread")
  113. try:
  114. # start a Queue object for emoji export
  115. emoji_queue = queue.Queue()
  116. # put the [filtered] emoji+formats (plus the index, cuz enumerate()) into the queue.
  117. for entry in enumerate(exporting_emoji):
  118. emoji_queue.put(entry)
  119. # initialise the amount of requested threads
  120. threads = []
  121. for i in range(num_threads):
  122. threads.append(ExportThread(emoji_queue, str(i), len(exporting_emoji),
  123. m, input_path, path, renderer,
  124. license_enabled))
  125. # keeps checking if the export queue is done.
  126. bar = log.get_progress_bar(max=len(exporting_emoji))
  127. while True:
  128. done = emoji_queue.empty()
  129. bar.goto(log.export_task_count)
  130. # if the thread has an error, properly terminate it
  131. # and then raise an error.
  132. for t in threads:
  133. if t.err is not None:
  134. for u in threads:
  135. u.kill()
  136. u.join()
  137. raise ValueError(f'Thread {t.name} failed: {t.err}') from t.err
  138. if done:
  139. break
  140. time.sleep(0.01) # wait a little before seeing if stuff is done again.
  141. # finish the stuff
  142. # - join the threads
  143. # - then finish the terminal stuff
  144. for t in threads:
  145. t.join()
  146. bar.goto(log.export_task_count)
  147. bar.finish()
  148. except (KeyboardInterrupt, SystemExit):
  149. # make sure all those threads are tidied before exiting the program.
  150. # also make sure the bar is finished so it doesnt eat the cursor.
  151. bar.finish()
  152. log.out(f'Stopping threads and tidying up...', 93)
  153. if threads:
  154. for t in threads:
  155. t.kill()
  156. t.join()
  157. raise
  158. # Copy exported emoji to cache
  159. if cache:
  160. # Calculate the real set of exported emoji to cache
  161. exports_to_cache = []
  162. for emoji, fs in exporting_emoji:
  163. cacheable_fs = cache.filter_cacheable_formats(fs, license_enabled)
  164. if cacheable_fs:
  165. exports_to_cache.append((emoji, cacheable_fs))
  166. # Save cacheable exports in cache
  167. if exports_to_cache:
  168. log.out(f'Copying {len(exports_to_cache)} exported emoji to '
  169. 'cache...', 36)
  170. for emoji, fs in exports_to_cache:
  171. for f in fs:
  172. # SVG has a license added during export, so this
  173. # flag passed to save_to_cache needs to be adjusted
  174. has_license = f in ('svg') and license_enabled
  175. export_path = format_path(path, emoji, f)
  176. cache.save_to_cache(emoji, f, export_path, has_license)
  177. log.out('done!', 32)
  178. if log.filtered_export_task_count > 0:
  179. log.out(f"-> {log.filtered_export_task_count} emoji have been implicitly or explicitly filtered out of this export task.", 34)
  180. log.export_task_count = 0
  181. log.filtered_export_task_count = 0