Scripts used to grab or read stats in RoK
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

rok-reader.py 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import argparse
  2. import glob
  3. import os
  4. from PIL import Image, ImageFilter
  5. from PIL.ImageOps import autocontrast, invert, grayscale, contain
  6. import pytesseract
  7. # ----
  8. # Program
  9. # ----
  10. PROGRAM_NAME = "RoK Reader"
  11. PROGRAM_VERSION = "0.3"
  12. PROGRAM_DESCRIPTION = "This program reads data from Rise of Kingdoms screenshots. It currently supports three user profile screenshots."
  13. # ----
  14. # Classes
  15. # ----
  16. class Box:
  17. def __init__(self, x, y, x2, y2):
  18. self.x = x
  19. self.y = y
  20. self.x2 = x2
  21. self.y2 = y2
  22. class RelativeBox:
  23. def __init__(self, x_distance, y_distance, width, height):
  24. self.x_distance = x_distance
  25. self.y_distance = y_distance
  26. self.width = width
  27. self.height = height
  28. # ----
  29. # Files
  30. # ----
  31. OUTPUT_PATH_PROFILE = "output-profile.csv"
  32. OUTPUT_PATH_MOREINFO = "output-more.csv"
  33. OUTPUT_PATH_KILLS = "output-kills.csv"
  34. # ----
  35. # Coordinates
  36. # ----
  37. # Name, Top Left, Bottom Right, Number, Invert, BonusRightTrim
  38. PROFILE_TARGETS = [
  39. ("ID", (1246, 375), (1445, 430), True, True, -10),
  40. ("Power", (1435, 585), (1733, 634), True, True, 0),
  41. ("Kill Points", (1806, 585), (2112, 633), True, True, 0),
  42. ("Alliance", (1025, 584), (1427, 637), False, True, 0),
  43. ("Civilization", (1884, 420), (2132, 486), False, True, 0)
  44. ]
  45. MOREINFO_TARGETS = [
  46. ("Power", (1305, 223), (1540, 274), True, True, 0),
  47. ("Kill Points", (1931, 222), (2188, 276), True, True, 0),
  48. ("Highest Power", (1815, 416), (2105, 483), True, True, 0),
  49. ("Victories", (1815, 515), (2105, 580), True, True, 0),
  50. ("Defeats", (1815, 613), (2105, 675), True, True, 0),
  51. ("Dead", (1815, 710), (2105, 771), True, True, 0),
  52. ("Scout Times", (1815, 806), (2105, 871), True, True, 0),
  53. ("Resources Gathered", (1815, 980), (2105, 1047), True, True, 0),
  54. ("Resource Assistance", (1815, 1077), (2105, 1144), True, True, 0),
  55. ("Alliance Help Times", (1815, 1174), (2105, 1238), True, True, 0)
  56. ]
  57. KILLS_TARGETS = [
  58. ("Kill Points", (1418, 312), (1694, 352), True, False, 0),
  59. ("T1 Kills", (1321, 637), (1538, 684), True, False, 0),
  60. ("T1 Kill Points", (1986, 637), (2212, 684), True, False, 0),
  61. ("T2 Kills", (1321, 702), (1538, 755), True, False, 0),
  62. ("T2 Kill Points", (1986, 702), (2212, 755), True, False, 0),
  63. ("T3 Kills", (1321, 770), (1538, 824), True, False, 0),
  64. ("T3 Kill Points", (1986, 770), (2212, 824), True, False, 0),
  65. ("T4 Kills", (1321, 847), (1538, 897), True, False, 0),
  66. ("T4 Kill Points", (1986, 847), (2212, 897), True, False, 0),
  67. ("T5 Kills", (1321, 918), (1538, 968), True, False, 0),
  68. ("T5 Kill Points", (1986, 918), (2212, 968), True, False, 0),
  69. ("Previous Kills", (1626, 985), (2228, 1039), False, False, 0)
  70. ]
  71. # ----
  72. # Functions
  73. # ----
  74. # Read text from a section of an image using Tesseract
  75. def read_string_from_image(file, box, is_number, inv, bonusRightTrim, debugFilePath):
  76. with Image.open(file) as image:
  77. # Crop to correct dimentions
  78. image = image.crop((box.x, box.y, box.x2, box.y2))
  79. # Switch to RGB mode
  80. rgbimage = Image.new("RGB", image.size, (255, 255, 255))
  81. rgbimage.paste(image, mask = image.split()[3])
  82. # Invert if flagged
  83. if inv: rgbimage = invert(rgbimage)
  84. # Apply filters
  85. rgbimage = grayscale(rgbimage)
  86. rgbimage = autocontrast(rgbimage, cutoff=(0, 75))
  87. bbox = autocontrast(invert(rgbimage), cutoff=(0, 90)).getbbox()
  88. if bbox: rgbimage = rgbimage.crop((bbox[0], bbox[1], bbox[2] + bonusRightTrim, bbox[3]))
  89. rgbimage = contain(rgbimage, (800, 800), method=1)
  90. rgbimage = rgbimage.filter(ImageFilter.EDGE_ENHANCE_MORE)
  91. rgbimage = rgbimage.filter(ImageFilter.SHARPEN)
  92. if arguments.debug:
  93. rgbimage.save(debugFilePath)
  94. if is_number:
  95. return pytesseract.image_to_string(rgbimage, config="--psm 6 -c tessedit_char_whitelist=0123456789,").strip().replace('\n', ' ').replace('\r', '').replace('.', '').replace(',', '').replace('\t', ' ').replace(' ', '')
  96. else:
  97. return pytesseract.image_to_string(rgbimage, config="--psm 6").strip().replace('\n', ' ').replace('\r', '').replace('\t', ' ')
  98. # ----
  99. # Arguments
  100. # ----
  101. parser = argparse.ArgumentParser(description = PROGRAM_DESCRIPTION)
  102. parser.add_argument("-p", "--project", help = "project name", required = True)
  103. parser.add_argument("-f", "--file", help = "file name (globs accepted)", required = True)
  104. parser.add_argument("-o", "--output", help = "output file")
  105. parser.add_argument("-v", "--verbose", help = "be verbose", default = False, action = "store_true")
  106. parser.add_argument("--debug", help = "save debug images", default = False, action = "store_true")
  107. arguments = parser.parse_args()
  108. # ----
  109. # Program
  110. # ----
  111. if __name__ == '__main__':
  112. # Create project folder
  113. projectFolder = "output/" + arguments.project + "/"
  114. if not os.path.exists(projectFolder):
  115. os.makedirs(projectFolder)
  116. debugFolder = "debug" + "/"
  117. if arguments.debug:
  118. if not os.path.exists(debugFolder):
  119. os.makedirs(debugFolder)
  120. # Get files
  121. screenshots_to_read = glob.glob(arguments.file, recursive=True)
  122. screenshot_count = len(screenshots_to_read)
  123. if screenshot_count < 1: sys.exit("No files found.")
  124. # Scrape
  125. if arguments.verbose: print("Scraping", screenshot_count, "files")
  126. for i, file in enumerate(screenshots_to_read):
  127. filename = os.path.basename(file)
  128. if arguments.verbose: print(i+1, "/", screenshot_count, ": ", filename, sep="")
  129. if "profile" in filename:
  130. targets = PROFILE_TARGETS
  131. output = OUTPUT_PATH_PROFILE
  132. elif "more" in filename:
  133. targets = MOREINFO_TARGETS
  134. output = OUTPUT_PATH_MOREINFO
  135. elif "kills" in filename:
  136. targets = KILLS_TARGETS
  137. output = OUTPUT_PATH_KILLS
  138. else:
  139. sys.exit("File name doesn't contain type")
  140. # TODO: bad time complexity
  141. exists = False
  142. with open(projectFolder + output, "a+", newline='', encoding='utf-8') as output_file:
  143. if filename not in output_file.read():
  144. if not arguments.debug: output_file.write(filename + "\t")
  145. for i, target in enumerate(targets):
  146. debugFile = os.path.splitext(filename)[0] + "_" + str(i) + ".png"
  147. string = read_string_from_image(file, Box(target[1][0], target[1][1], target[2][0], target[2][1]), target[3], target[4], target[5], debugFolder + debugFile)
  148. if arguments.verbose: print(" ", target[0], ": ", string, sep="")
  149. if i and not arguments.debug: output_file.write("\t")
  150. if not arguments.debug: output_file.write(string)
  151. if not arguments.debug: output_file.write("\n")
  152. else:
  153. if arguments.verbose: print(" ", "already scraped.")