retroarch.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import json
  2. import logging
  3. import os
  4. from datetime import datetime, timedelta
  5. from typing import List
  6. import pytz
  7. from dateutil.parser import ParserError, parse
  8. from django.apps import apps
  9. from vrobbler.apps.scrobbles.utils import convert_to_seconds
  10. from vrobbler.apps.videogames.scrapers import scrape_game_name_from_adb
  11. from vrobbler.apps.videogames.utils import get_or_create_videogame
  12. logger = logging.getLogger(__name__)
  13. from videogames.models import VideoGame
  14. def load_game_data(directory_path: str, user_tz=None) -> dict:
  15. """Given a path to a directory, cycle through each found lrtl file and
  16. generate game data.
  17. Example json file as follows:
  18. Name: "Sonic The Hedgehog 2 (World).lrtl"
  19. Contents:
  20. {
  21. "version": "1.0",
  22. "runtime": "0:20:19",
  23. "last_played": "2023-05-23 15:30:15"
  24. }
  25. """
  26. directory = os.fsencode(directory_path)
  27. games = {}
  28. if not user_tz:
  29. user_tz = pytz.utc
  30. for file in os.listdir(directory):
  31. filename = os.fsdecode(file)
  32. if not filename.endswith("lrtl"):
  33. logger.info(
  34. f"Found non-gamelog file extension, skipping {filename}"
  35. )
  36. continue
  37. game_name = filename.split(".lrtl")[0].split(" (")[0]
  38. with open("".join([directory_path, filename])) as f:
  39. games[game_name] = json.load(f)
  40. # Convert runtime to seconds
  41. games[game_name]["runtime"] = convert_to_seconds(
  42. games[game_name]["runtime"]
  43. )
  44. # Convert last_played to datetime in UTC
  45. games[game_name]["last_played"] = (
  46. parse(games[game_name]["last_played"])
  47. .replace(tzinfo=user_tz)
  48. .astimezone(pytz.utc)
  49. )
  50. return games
  51. def import_retroarch_lrtl_files(playlog_path: str, user_id: int) -> List[dict]:
  52. """Given a path to Retroarch lrtl game log file data,
  53. gather
  54. For each found log file, we'll do:
  55. 1. Look up game, create if it doesn't exist
  56. 2. Check for existing scrobbles
  57. 3. Create new scrobble if last_played != last_scrobble.timestamp
  58. 4. Calculate scrobble time from runtime - last_scrobble.long_play_time
  59. """
  60. Scrobble = apps.get_model("scrobbles", "Scrobble")
  61. game_logs = load_game_data(playlog_path)
  62. found_game = None
  63. new_scrobbles = []
  64. for game_name, game_data in game_logs.items():
  65. # Use the retroarch name, because we can't change those but may want to
  66. # tweak the found game
  67. mame_name = scrape_game_name_from_adb(game_name)
  68. if mame_name:
  69. game_name = mame_name
  70. found_game = VideoGame.objects.filter(retroarch_name=game_name).first()
  71. if not found_game:
  72. found_game = get_or_create_videogame(game_name)
  73. if found_game:
  74. found_game.retroarch_name = game_name
  75. found_game.save(update_fields=["retroarch_name"])
  76. if found_game:
  77. found_scrobble = found_game.scrobble_set.filter(
  78. timestamp=game_data["last_played"]
  79. )
  80. if found_scrobble:
  81. logger.info(
  82. f"Found scrobble for {game_name} with timestamp {game_data['last_played']}, not scrobbling"
  83. )
  84. continue
  85. last_scrobble = found_game.scrobble_set.last()
  86. delta_runtime = 0
  87. if last_scrobble:
  88. delta_runtime = last_scrobble.long_play_seconds
  89. playback_position_seconds = game_data["runtime"] - delta_runtime
  90. stop_timestamp = game_data["last_played"] + timedelta(
  91. seconds=playback_position_seconds
  92. )
  93. if playback_position_seconds < 30:
  94. logger.info(
  95. f"Video game {found_game.id} played for less than 30 seconds, skipping"
  96. )
  97. new_scrobbles.append(
  98. Scrobble(
  99. video_game_id=found_game.id,
  100. timestamp=game_data["last_played"],
  101. stop_timestamp=stop_timestamp,
  102. playback_position_seconds=playback_position_seconds,
  103. played_to_completion=True,
  104. in_progress=False,
  105. long_play_seconds=game_data["runtime"],
  106. user_id=user_id,
  107. source="Retroarch",
  108. source_id="Imported from Retroarch play log file",
  109. )
  110. )
  111. created_scrobbles = Scrobble.objects.bulk_create(new_scrobbles)
  112. return new_scrobbles