utils.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. from games.constants import (
  2. ENGLISH_PATCHED_KEYWORDS,
  3. HACK_KEYWORDS,
  4. REGION_KEYWORDS,
  5. UNDUB_KEYWORDS,
  6. )
  7. import os
  8. import xml.etree.ElementTree as ET
  9. import subprocess
  10. import re
  11. from dateutil import parser
  12. from django.conf import settings
  13. from .models import Developer, Game, GameSystem, Genre, Publisher, GameCollection
  14. import logging
  15. logger = logging.Logger(__name__)
  16. def update_media_root_for_import(file_path):
  17. """Given a file path, re-write it for our app MEDIA_ROOT"""
  18. suffix = ""
  19. if file_path:
  20. suffix = file_path.split("/media/")[-1]
  21. return suffix
  22. def import_gamelist_file_to_db_for_system(
  23. game_system_slug, file_path=None, full_scan=False
  24. ):
  25. imported_games = []
  26. not_imported_games = []
  27. if not file_path:
  28. file_path = os.path.join(settings.ROMS_DIR, game_system_slug, "gamelist.xml")
  29. if not os.path.exists(file_path):
  30. logger.info(
  31. "File path for {game_system_slug} had no gamelist.xml file, run a scraper first!"
  32. )
  33. return
  34. gamelist = ET.parse(file_path)
  35. game_system = GameSystem.objects.filter(retropie_slug=game_system_slug).first()
  36. if not game_system:
  37. defaults = settings.GAME_SYSTEM_DEFAULTS.get(game_system_slug, None)
  38. full_system_name = game_system_slug
  39. if defaults:
  40. full_system_name = defaults.get("name", game_system_slug)
  41. game_system = GameSystem.objects.create(
  42. name=full_system_name, retropie_slug=game_system_slug
  43. )
  44. games = gamelist.findall("game")
  45. for game in games:
  46. name = game.find("name").text
  47. game_path = game.find("path").text.lower()
  48. try:
  49. obj, created = Game.objects.get_or_create(
  50. name=name, game_system=game_system
  51. )
  52. except Game.MultipleObjectsReturned:
  53. logger.warning(
  54. f"While importing {name} for {game_system}, duplicate entry found"
  55. )
  56. print(f"While importing {name} for {game_system}, duplicate entry found")
  57. continue
  58. if not created and not full_scan:
  59. not_imported_games.append(obj)
  60. logger.info(f"Not updating {obj}, use full-scan to update")
  61. print(f"Not updating {obj}, use full-scan to update")
  62. continue
  63. elif not created and full_scan:
  64. print(f"Updating {obj}")
  65. else:
  66. print(f"Adding {obj}")
  67. region = Game.Region.X.name
  68. if any(us in game_path for us in REGION_KEYWORDS["US"]):
  69. region = Game.Region.US.name
  70. if any(jp in game_path for jp in REGION_KEYWORDS["JP"]):
  71. region = Game.Region.JP.name
  72. if any(eu in game_path for eu in REGION_KEYWORDS["EU"]):
  73. region = Game.Region.EU.name
  74. english_patched = any(
  75. key in game_path.lower() for key in ENGLISH_PATCHED_KEYWORDS
  76. )
  77. patch_version = None
  78. if english_patched:
  79. version_re = re.search("(?= v)(.*?)(?=\))", game_path.lower())
  80. if version_re:
  81. patch_version = version_re.group(0)[2:]
  82. undub = any(key in game_path.lower() for key in UNDUB_KEYWORDS)
  83. hack = any(key in game_path.lower() for key in HACK_KEYWORDS)
  84. release_date_str = game.find("releasedate").text
  85. developer_str = game.find("developer").text
  86. publisher_str = game.find("publisher").text
  87. genres_str = game.find("genre").text
  88. rating_str = game.find("rating").text
  89. players_str = "1"
  90. if game.find("players"):
  91. players_str = game.find("players").text
  92. try:
  93. kid_game = game.find("kidgame").text == "true"
  94. except AttributeError:
  95. kid_game = False
  96. genre_list = []
  97. if genres_str:
  98. genre_str_list = genres_str.split(", ")
  99. for genre_str in genre_str_list:
  100. genre, _created = Genre.objects.get_or_create(name=genre_str)
  101. genre_list.append(genre)
  102. players = int(players_str) if players_str else 1
  103. rating = float(rating_str) if rating_str else None
  104. publisher = None
  105. if publisher_str:
  106. publisher, _created = Publisher.objects.get_or_create(name=publisher_str)
  107. developer = None
  108. if developer_str:
  109. developer, _created = Developer.objects.get_or_create(name=developer_str)
  110. release_date = parser.parse(release_date_str) if release_date_str else None
  111. description = game.find("desc").text
  112. screenshot_path = update_media_root_for_import(game.find("image").text)
  113. rom_path = update_media_root_for_import(game.find("path").text)
  114. video_path_elem = game.find("video")
  115. video_path = ""
  116. if video_path_elem:
  117. video_path = update_media_root_for_import(video_path_elem.text)
  118. marquee_path = update_media_root_for_import(game.find("marquee").text)
  119. obj.game_system = game_system
  120. obj.developer = developer
  121. obj.publisher = publisher
  122. obj.players = players
  123. obj.description = description
  124. obj.release_date = release_date
  125. obj.rating = rating
  126. obj.genre.set(genre_list)
  127. obj.screenshot = screenshot_path
  128. obj.rom_file = rom_path
  129. obj.video = video_path
  130. obj.marquee = marquee_path
  131. obj.kid_game = kid_game
  132. obj.english_patched = english_patched
  133. obj.english_patched_version = patch_version
  134. obj.hack = hack
  135. obj.undub = undub
  136. obj.region = region
  137. obj.save()
  138. imported_games.append(obj)
  139. return {"imported": imported_games, "not_imported": not_imported_games}
  140. def export_gamelist_file_to_path_for_system(game_system_slug, file_path=None):
  141. exported_games = []
  142. game_system = GameSystem.objects.get(retropie_slug=game_system_slug)
  143. if not file_path:
  144. file_path = f"/tmp/{game_system_slug}-gamelist.xml"
  145. # file_path = os.path.join(settings.ROMS_DIR, game_system_slug, "gamelist.xml")
  146. root = ET.Element("gameList")
  147. tree = ET.ElementTree(root)
  148. tree.write(file_path)
  149. games = Game.objects.filter(game_system=game_system)
  150. for game in games:
  151. game_node = ET.SubElement(root, "game")
  152. genre_str = ", ".join(game.genre.all().values_list("name", flat=True))
  153. release_date_str = ""
  154. if game.release_date:
  155. release_date_str = game.release_date.strftime("%Y%m%dT00000")
  156. ET.SubElement(game_node, "path").text = (
  157. game.rom_file.path if game.rom_file else ""
  158. )
  159. ET.SubElement(game_node, "name").text = game.name
  160. ET.SubElement(game_node, "thumbnail").text = ""
  161. ET.SubElement(game_node, "image").text = (
  162. game.screenshot.path if game.screenshot else ""
  163. )
  164. ET.SubElement(game_node, "marquee").text = (
  165. game.marquee.path if game.marquee else ""
  166. )
  167. ET.SubElement(game_node, "video").text = game.video.path if game.video else ""
  168. ET.SubElement(game_node, "rating").text = str(game.rating)
  169. ET.SubElement(game_node, "desc").text = game.description
  170. ET.SubElement(game_node, "releasedate").text = release_date_str
  171. ET.SubElement(game_node, "developer").text = str(game.developer)
  172. ET.SubElement(game_node, "publisher").text = str(game.publisher)
  173. ET.SubElement(game_node, "genre").text = genre_str
  174. ET.SubElement(game_node, "players").text = str(game.players)
  175. if game.kid_game:
  176. ET.SubElement(game_node, "kidgame").text = "true"
  177. exported_games.append(game)
  178. tree = ET.ElementTree(root)
  179. tree.write(file_path, xml_declaration=True, encoding="utf-8")
  180. return exported_games, file_path
  181. def skyscrape_console(game_system_slug):
  182. scraper_config = settings.SCRAPER_CONFIG_FILE
  183. scraper_binary = settings.SCRAPER_BIN_PATH
  184. scraper_site = settings.SCRAPER_SITE
  185. # If the config file is relative, append our base dir
  186. if scraper_config[0] != "/":
  187. scraper_config = os.path.join(settings.BASE_DIR, scraper_config)
  188. if not os.path.exists(scraper_config):
  189. logger.info(f"Config file not found at {scraper_config}")
  190. return
  191. logger.info(f"Scraping game info using configuration file from {scraper_config}")
  192. scrape_output = subprocess.run(
  193. [
  194. scraper_binary,
  195. "-c",
  196. f"{scraper_config}",
  197. "-s",
  198. f"{scraper_site}",
  199. "-f",
  200. "emulationstation",
  201. "-p",
  202. f"{game_system_slug}",
  203. ],
  204. capture_output=True,
  205. )
  206. load_output = subprocess.run(
  207. [
  208. scraper_binary,
  209. "-c",
  210. f"{scraper_config}",
  211. "-f",
  212. "emulationstation",
  213. "-p",
  214. f"{game_system_slug}",
  215. ],
  216. capture_output=True,
  217. )
  218. # TODO We should progressively pipe output to a log file instead of this
  219. # print(scrape_output)
  220. # print(load_output)
  221. print(f"Scraped info for {game_system_slug}")
  222. return scrape_output, load_output
  223. def export_collections(dryrun=True):
  224. exported = []
  225. for collection in GameCollection.objects.all():
  226. collection.export_to_file(dryrun)
  227. exported.append(collection)
  228. return exported