utils.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import logging
  2. import re
  3. from typing import Optional
  4. from music.musicbrainz import (
  5. lookup_album_dict_from_mb,
  6. lookup_artist_from_mb,
  7. lookup_track_from_mb,
  8. )
  9. from music.constants import VARIOUS_ARTIST_DICT
  10. from scrobbles.utils import convert_to_seconds
  11. logger = logging.getLogger(__name__)
  12. from music.models import Album, Artist, Track
  13. def clean_artist_name(name: str) -> str:
  14. """Remove featured names from artist string."""
  15. if "feat." in name.lower():
  16. name = re.split("feat.", name, flags=re.IGNORECASE)[0].strip()
  17. if "featuring" in name.lower():
  18. name = re.split("featuring", name, flags=re.IGNORECASE)[0].strip()
  19. if "&" in name.lower():
  20. name = re.split("&", name, flags=re.IGNORECASE)[0].strip()
  21. return name
  22. # TODO These are depreacted, remove them eventually
  23. def get_or_create_artist(name: str, mbid: str = "") -> Artist:
  24. """Get an Artist object from the database.
  25. Check if an artist with this name or Musicbrainz ID already exists.
  26. Otherwise, go lookup artist data from Musicbrainz and create one.
  27. """
  28. artist = None
  29. name = clean_artist_name(name)
  30. # Check for name/mbid combo, just mbid and then just name
  31. artist = Artist.objects.filter(name=name, mbid=mbid).first()
  32. if not artist:
  33. artist = Artist.objects.filter(musicbrainz_id=mbid).first()
  34. if not artist:
  35. artist = Artist.objects.filter(name=name).first()
  36. # Does not exist, look it up from Musicbrainz
  37. if not artist:
  38. artist_dict = lookup_artist_from_mb(name)
  39. mbid = mbid or artist_dict.get("id", "")
  40. if mbid:
  41. artist = Artist.objects.filter(musicbrainz_id=mbid).first()
  42. if not artist:
  43. artist = Artist.objects.create(name=name, musicbrainz_id=mbid)
  44. # TODO maybe this should be spun off into an async task?
  45. artist.fix_metadata()
  46. return artist
  47. # TODO These are depreacted, remove them eventually
  48. def get_or_create_album(
  49. name: str, artist: Artist, mbid: str = None
  50. ) -> Optional[Album]:
  51. album = None
  52. album_dict = lookup_album_dict_from_mb(name, artist_name=artist.name)
  53. name = name or album_dict.get("title", None)
  54. if not name:
  55. logger.debug(
  56. f"Cannot get or create album by {artist} with no name ({name})"
  57. )
  58. return
  59. album = Album.objects.filter(
  60. musicbrainz_id=mbid, name=name, artists__in=[artist]
  61. ).first()
  62. if not album:
  63. mbid_group = album_dict.get("mb_group_id")
  64. album = Album.objects.filter(
  65. musicbrainz_releasegroup_id=mbid_group
  66. ).first()
  67. if not album and name:
  68. mbid = mbid or album_dict["mb_id"]
  69. album, album_created = Album.objects.get_or_create(musicbrainz_id=mbid)
  70. if album_created:
  71. album.name = name
  72. album.year = album_dict["year"]
  73. album.musicbrainz_releasegroup_id = album_dict["mb_group_id"]
  74. album.musicbrainz_albumartist_id = artist.musicbrainz_id
  75. album.save(
  76. update_fields=[
  77. "name",
  78. "musicbrainz_id",
  79. "year",
  80. "musicbrainz_releasegroup_id",
  81. "musicbrainz_albumartist_id",
  82. ]
  83. )
  84. album.artists.add(artist)
  85. album.fix_album_artist()
  86. album.fetch_artwork()
  87. album.scrape_allmusic()
  88. if not album:
  89. logger.warn(f"No album found for {name} and {mbid}")
  90. album.fix_album_artist()
  91. return album
  92. # TODO These are depreacted, remove them eventually
  93. def get_or_create_track(post_data: dict, post_keys: dict) -> Track:
  94. try:
  95. track_run_time_seconds = int(
  96. post_data.get(post_keys.get("RUN_TIME"), 0)
  97. )
  98. except ValueError: # Sometimes we get run time as a string like "01:35"
  99. track_run_time_seconds = convert_to_seconds(
  100. post_data.get(post_keys.get("RUN_TIME"), 0)
  101. )
  102. artist_name = post_data.get(post_keys.get("ARTIST_NAME"), "")
  103. artist_mb_id = post_data.get(post_keys.get("ARTIST_MB_ID"), "")
  104. album_title = post_data.get(post_keys.get("ALBUM_NAME"), "")
  105. album_mb_id = post_data.get(post_keys.get("ALBUM_MB_ID"), "")
  106. track_title = post_data.get(post_keys.get("TRACK_TITLE"), "")
  107. track_mb_id = post_data.get(post_keys.get("TRACK_MB_ID"), "")
  108. artist = Artist.find_or_create(artist_name, artist_mb_id)
  109. album = None
  110. # We may get no album ID or title, in which case, skip
  111. if album_mb_id or album_title:
  112. album = Album.find_or_create(
  113. album_title, str(artist.name), album_mb_id
  114. )
  115. track = None
  116. if not track_mb_id and album:
  117. try:
  118. track_mb_id = lookup_track_from_mb(
  119. track_title,
  120. artist.musicbrainz_id,
  121. album.musicbrainz_id,
  122. ).get("id", 0)
  123. except TypeError:
  124. pass
  125. if not track_title and not track_mb_id:
  126. logger.info(
  127. "Cannot find track without either title or MB ID",
  128. extra={"post_data": post_data},
  129. )
  130. return
  131. if track_mb_id:
  132. track = Track.objects.filter(musicbrainz_id=track_mb_id).first()
  133. if not track and track_title:
  134. track = Track.objects.filter(title=track_title, artist=artist).first()
  135. if not track:
  136. track = Track.objects.create(
  137. title=track_title,
  138. artist=artist,
  139. album=album,
  140. musicbrainz_id=track_mb_id,
  141. run_time_seconds=track_run_time_seconds,
  142. )
  143. return track
  144. def get_or_create_various_artists() -> Artist:
  145. artist = Artist.objects.filter(name="Various Artists").first()
  146. if not artist:
  147. artist = Artist.objects.create(**VARIOUS_ARTIST_DICT)
  148. logger.info("Created Various Artists placeholder")
  149. return artist