utils.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. from games.constants import (
  2. ENGLISH_PATCHED_KEYWORDS,
  3. HACK_KEYWORDS,
  4. KNOWN_DUPLICATE_TITLES,
  5. REGION_KEYWORDS,
  6. UNDUB_KEYWORDS,
  7. )
  8. import os
  9. import xml.etree.ElementTree as ET
  10. import subprocess
  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, region=region
  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(game)
  60. logger.info(f"Found game {game} and not doing full scan, so skipping")
  61. continue
  62. region = Game.Region.X.name
  63. if any(us in game_path for us in REGION_KEYWORDS["US"]):
  64. region = Game.Region.US.name
  65. if any(jp in game_path for jp in REGION_KEYWORDS["JP"]):
  66. region = Game.Region.JP.name
  67. if any(eu in game_path for eu in REGION_KEYWORDS["EU"]):
  68. region = Game.Region.EU.name
  69. english_patched = game_path.lower() in ENGLISH_PATCHED_KEYWORDS
  70. undub = game_path.lower() in UNDUB_KEYWORDS
  71. hack = game_path.lower() in HACK_KEYWORDS
  72. release_date_str = game.find("releasedate").text
  73. developer_str = game.find("developer").text
  74. publisher_str = game.find("publisher").text
  75. genres_str = game.find("genre").text
  76. rating_str = game.find("rating").text
  77. players_str = "1"
  78. if game.find("players"):
  79. players_str = game.find("players").text
  80. try:
  81. kid_game = game.find("kidgame").text == "true"
  82. except AttributeError:
  83. kid_game = False
  84. genre_list = []
  85. if genres_str:
  86. genre_str_list = genres_str.split(", ")
  87. for genre_str in genre_str_list:
  88. genre, _created = Genre.objects.get_or_create(name=genre_str)
  89. genre_list.append(genre)
  90. players = int(players_str) if players_str else 1
  91. rating = float(rating_str) if rating_str else None
  92. publisher = None
  93. if publisher_str:
  94. publisher, _created = Publisher.objects.get_or_create(name=publisher_str)
  95. developer = None
  96. if developer_str:
  97. developer, _created = Developer.objects.get_or_create(name=developer_str)
  98. release_date = parser.parse(release_date_str) if release_date_str else None
  99. description = game.find("desc").text
  100. screenshot_path = update_media_root_for_import(game.find("image").text)
  101. rom_path = update_media_root_for_import(game.find("path").text)
  102. video_path_elem = game.find("video")
  103. video_path = ""
  104. if video_path_elem:
  105. video_path = update_media_root_for_import(video_path_elem.text)
  106. marquee_path = update_media_root_for_import(game.find("marquee").text)
  107. obj.game_system = game_system
  108. obj.developer = developer
  109. obj.publisher = publisher
  110. obj.players = players
  111. obj.description = description
  112. obj.release_date = release_date
  113. obj.rating = rating
  114. obj.genre.set(genre_list)
  115. obj.screenshot = screenshot_path
  116. obj.rom_file = rom_path
  117. obj.video = video_path
  118. obj.marquee = marquee_path
  119. obj.kid_game = kid_game
  120. obj.english_patched = english_patched
  121. obj.hack = hack
  122. obj.undub = undub
  123. obj.region = region
  124. obj.save()
  125. imported_games.append(game)
  126. return {"imported": imported_games, "not_imported": not_imported_games}
  127. def export_gamelist_file_to_path_for_system(game_system_slug, file_path=None):
  128. exported_games = []
  129. game_system = GameSystem.objects.get(retropie_slug=game_system_slug)
  130. if not file_path:
  131. file_path = f"/tmp/{game_system_slug}-gamelist.xml"
  132. # file_path = os.path.join(settings.ROMS_DIR, game_system_slug, "gamelist.xml")
  133. root = ET.Element("gameList")
  134. tree = ET.ElementTree(root)
  135. tree.write(file_path)
  136. games = Game.objects.filter(game_system=game_system)
  137. for game in games:
  138. game_node = ET.SubElement(root, "game")
  139. genre_str = ", ".join(game.genre.all().values_list("name", flat=True))
  140. release_date_str = ""
  141. if game.release_date:
  142. release_date_str = game.release_date.strftime("%Y%m%dT00000")
  143. ET.SubElement(game_node, "path").text = (
  144. game.rom_file.path if game.rom_file else ""
  145. )
  146. ET.SubElement(game_node, "name").text = game.name
  147. ET.SubElement(game_node, "thumbnail").text = ""
  148. ET.SubElement(game_node, "image").text = (
  149. game.screenshot.path if game.screenshot else ""
  150. )
  151. ET.SubElement(game_node, "marquee").text = (
  152. game.marquee.path if game.marquee else ""
  153. )
  154. ET.SubElement(game_node, "video").text = game.video.path if game.video else ""
  155. ET.SubElement(game_node, "rating").text = str(game.rating)
  156. ET.SubElement(game_node, "desc").text = game.description
  157. ET.SubElement(game_node, "releasedate").text = release_date_str
  158. ET.SubElement(game_node, "developer").text = str(game.developer)
  159. ET.SubElement(game_node, "publisher").text = str(game.publisher)
  160. ET.SubElement(game_node, "genre").text = genre_str
  161. ET.SubElement(game_node, "players").text = str(game.players)
  162. if game.kid_game:
  163. ET.SubElement(game_node, "kidgame").text = "true"
  164. exported_games.append(game)
  165. tree = ET.ElementTree(root)
  166. tree.write(file_path, xml_declaration=True, encoding="utf-8")
  167. return exported_games, file_path
  168. def skyscrape_console(game_system_slug):
  169. scraper_config = settings.SCRAPER_CONFIG_FILE
  170. scraper_binary = settings.SCRAPER_BIN_PATH
  171. scraper_site = settings.SCRAPER_SITE
  172. # If the config file is relative, append our base dir
  173. if scraper_config[0] != "/":
  174. scraper_config = os.path.join(settings.BASE_DIR, scraper_config)
  175. if not os.path.exists(scraper_config):
  176. logger.info(f"Config file not found at {scraper_config}")
  177. return
  178. logger.info(f"Scraping game info using configuration file from {scraper_config}")
  179. scrape_output = subprocess.run(
  180. [
  181. scraper_binary,
  182. "-c",
  183. f"{scraper_config}",
  184. "-s",
  185. f"{scraper_site}",
  186. "-f",
  187. "emulationstation",
  188. "-p",
  189. f"{game_system_slug}",
  190. ],
  191. capture_output=True,
  192. )
  193. load_output = subprocess.run(
  194. [
  195. scraper_binary,
  196. "-c",
  197. f"{scraper_config}",
  198. "-f",
  199. "emulationstation",
  200. "-p",
  201. f"{game_system_slug}",
  202. ],
  203. capture_output=True,
  204. )
  205. # TODO We should progressively pipe output to a log file instead of this
  206. # print(scrape_output)
  207. # print(load_output)
  208. print(f"Scraped info for {game_system_slug}")
  209. return scrape_output, load_output
  210. def export_collections(dryrun=True):
  211. exported = []
  212. for collection in GameCollection.objects.all():
  213. collection.export_to_file(dryrun)
  214. exported.append(collection)
  215. return exported