utils.py 8.9 KB

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