db: Add a Tagger tool
This tool wraps around a mutagen.File to read tags and translate them into our database playlists. Implements: #41 ("Check for new or modified tags during startup") Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
24c1a31367
commit
300ee18569
|
@ -0,0 +1,99 @@
|
|||
# Copyright 2022 (c) Anna Schumaker
|
||||
"""A wrapper around Mutagen to help us read tags."""
|
||||
from gi.repository import GObject
|
||||
from .. import audio
|
||||
from . import albums
|
||||
from . import artists
|
||||
from . import decades
|
||||
from . import media
|
||||
from . import genres
|
||||
from . import years
|
||||
|
||||
|
||||
class Tags:
|
||||
"""Translate the audio.tagger._Tags object into Playlists."""
|
||||
|
||||
def __init__(self, db: GObject.TYPE_PYOBJECT,
|
||||
raw_tags: audio.tagger._Tags):
|
||||
"""Initialize the Tags object."""
|
||||
self.db = db
|
||||
|
||||
with self.db:
|
||||
self.album = self.get_album(raw_tags.album)
|
||||
self.album_artists = [self.get_artist(artist)
|
||||
for artist in raw_tags.album.artists]
|
||||
self.artists = [self.get_artist(artist)
|
||||
for artist in raw_tags.artists]
|
||||
self.decade = self.get_decade(raw_tags.year)
|
||||
self.genres = list(filter(None, [self.get_genre(genre)
|
||||
for genre in raw_tags.genres]))
|
||||
self.medium = self.get_medium(raw_tags.medium)
|
||||
self.year = self.get_year(raw_tags.year)
|
||||
|
||||
self.__update_album_artists()
|
||||
|
||||
def __update_album_artists(self) -> None:
|
||||
if self.album is not None:
|
||||
old = set(self.album.get_artists())
|
||||
new = set(self.album_artists)
|
||||
|
||||
for artist in old - new:
|
||||
artist.remove_album(self.album)
|
||||
for artist in new - old:
|
||||
artist.add_album(self.album)
|
||||
|
||||
def get_album(self, raw_album: audio.tagger._Album) -> albums.Album | None:
|
||||
"""Convert the raw album into an Album object."""
|
||||
if raw_album.name == "":
|
||||
return None
|
||||
|
||||
cover = raw_album.cover if raw_album.cover.is_file() else None
|
||||
album = self.db.albums.lookup(raw_album.name, raw_album.artist,
|
||||
raw_album.release, mbid=raw_album.mbid)
|
||||
if album is not None:
|
||||
if album.cover != cover:
|
||||
album.cover = cover
|
||||
return album
|
||||
return self.db.albums.create(raw_album.name, raw_album.artist,
|
||||
raw_album.release, mbid=raw_album.mbid,
|
||||
cover=cover)
|
||||
|
||||
def get_artist(self, raw_artist: audio.tagger._Artist) \
|
||||
-> artists.Artist | None:
|
||||
"""Convert the raw artist into an Artist object."""
|
||||
artist = self.db.artists.lookup(raw_artist.name, mbid=raw_artist.mbid)
|
||||
if artist is not None:
|
||||
return artist
|
||||
return self.db.artists.create(raw_artist.name, mbid=raw_artist.mbid)
|
||||
|
||||
def get_decade(self, raw_year: int | None) -> decades.Decade | None:
|
||||
"""Convert the raw year into a Decade object."""
|
||||
if raw_year:
|
||||
decade = self.db.decades.lookup(raw_year)
|
||||
return decade if decade else self.db.decades.create(raw_year)
|
||||
|
||||
def get_genre(self, raw_genre: str) -> genres.Genre:
|
||||
"""Convert the raw genre names into Genre objects."""
|
||||
genre = self.db.genres.lookup(raw_genre)
|
||||
return genre if genre else self.db.genres.create(raw_genre)
|
||||
|
||||
def get_medium(self, raw_medium: audio.tagger._Medium) \
|
||||
-> media.Medium | None:
|
||||
"""Convert the raw medium into a Medium object."""
|
||||
if self.album is None:
|
||||
return None
|
||||
|
||||
medium = self.db.media.lookup(self.album, number=raw_medium.number,
|
||||
type=raw_medium.type)
|
||||
if medium is not None:
|
||||
medium.rename(raw_medium.name)
|
||||
return medium
|
||||
return self.db.media.create(self.album, raw_medium.name,
|
||||
number=raw_medium.number,
|
||||
type=raw_medium.type)
|
||||
|
||||
def get_year(self, raw_year: int | None) -> years.Year | None:
|
||||
"""Convert the raw year into a Year object."""
|
||||
if raw_year:
|
||||
year = self.db.years.lookup(raw_year)
|
||||
return year if year else self.db.years.create(raw_year)
|
|
@ -0,0 +1,146 @@
|
|||
# Copyright 2022 (c) Anna Schumaker
|
||||
"""Tests our Mutagen wrapper."""
|
||||
import pathlib
|
||||
import unittest.mock
|
||||
import emmental.db.tagger
|
||||
import tests.util
|
||||
|
||||
|
||||
class TestTags(tests.util.TestCase):
|
||||
"""Test the Tags object."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
super().setUp()
|
||||
self.file = pathlib.Path("/a/b/c/track.ogg")
|
||||
|
||||
def make_tags(self, raw_tags: dict) -> emmental.db.tagger.Tags:
|
||||
"""Set up and return our Tags object."""
|
||||
audio_tags = emmental.audio.tagger._Tags(self.file, raw_tags)
|
||||
return emmental.db.tagger.Tags(self.sql, audio_tags)
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the Tags object was set up properly."""
|
||||
tags = self.make_tags({})
|
||||
self.assertEqual(tags.db, self.sql)
|
||||
|
||||
self.assertIsNone(tags.album)
|
||||
self.assertIsNone(tags.decade)
|
||||
self.assertIsNone(tags.medium)
|
||||
self.assertIsNone(tags.year)
|
||||
|
||||
self.assertListEqual(tags.album_artists, [])
|
||||
self.assertListEqual(tags.artists, [])
|
||||
self.assertListEqual(tags.genres, [])
|
||||
|
||||
def test_album(self):
|
||||
"""Test that the album was tagged properly."""
|
||||
raw_tags = {"album": ["Album Name"],
|
||||
"musicbrainz_releasegroupid": ["ab-cd-ef"],
|
||||
"albumartist": ["Album Artist"],
|
||||
"date": ["1988-06"]}
|
||||
|
||||
with unittest.mock.patch.object(pathlib.Path, "is_file",
|
||||
return_value=True):
|
||||
tags = self.make_tags(raw_tags)
|
||||
self.assertIsInstance(tags.album, emmental.db.albums.Album)
|
||||
self.assertEqual(tags.album.name, "Album Name")
|
||||
self.assertEqual(tags.album.mbid, "ab-cd-ef")
|
||||
self.assertEqual(tags.album.artist, "Album Artist")
|
||||
self.assertEqual(tags.album.release, "1988-06")
|
||||
self.assertEqual(tags.album.cover, pathlib.Path("/a/b/c/cover.jpg"))
|
||||
|
||||
self.assertEqual(self.make_tags(raw_tags).album, tags.album)
|
||||
self.assertIsNone(tags.album.cover)
|
||||
|
||||
def test_album_artists(self):
|
||||
"""Test that album artists were tagged and updated properly."""
|
||||
raw_tags = {"album": ["Album Name"],
|
||||
"musicbrainz_releasegroupid": ["ab-cd-ef"],
|
||||
"albumartist": ["Artist 1", "Artist 2"],
|
||||
"musicbrainz_albumartistid": ["gh-ij-kl", "mn-op-qr"],
|
||||
"date": ["1988-06"]}
|
||||
|
||||
tags = self.make_tags(raw_tags)
|
||||
self.assertEqual(len(tags.album_artists), 2)
|
||||
for i, artist in enumerate(tags.album_artists):
|
||||
with self.subTest(i=i):
|
||||
self.assertIsInstance(artist, emmental.db.artists.Artist)
|
||||
self.assertEqual(artist.name, f"Artist {i+1}")
|
||||
self.assertEqual(artist.mbid,
|
||||
"gh-ij-kl" if i == 0 else "mn-op-qr")
|
||||
|
||||
self.assertListEqual(tags.album.get_artists(), tags.album_artists)
|
||||
self.assertListEqual(self.make_tags(raw_tags).album_artists,
|
||||
tags.album_artists)
|
||||
|
||||
raw_tags["albumartist"] = ["Artist 1", "Artist 3"]
|
||||
raw_tags["musicbrainz_albumartistid"] = ["gh-ij-kl", "st-uv-wx"]
|
||||
updated = self.make_tags(raw_tags)
|
||||
self.assertEqual(updated.album, tags.album)
|
||||
self.assertListEqual(tags.album.get_artists(), updated.album_artists)
|
||||
|
||||
def test_artists(self):
|
||||
"""Test that artists were tagged properly."""
|
||||
raw_tags = {"artists": ["Artist 1", "Artist 2"],
|
||||
"albumartist": ["Artist 3"]}
|
||||
|
||||
tags = self.make_tags(raw_tags)
|
||||
self.assertEqual(len(tags.artists), 3)
|
||||
for i, artist in enumerate(tags.artists):
|
||||
with self.subTest(i=i):
|
||||
self.assertIsInstance(artist, emmental.db.artists.Artist)
|
||||
self.assertEqual(artist.name, f"Artist {i+1}")
|
||||
|
||||
self.assertListEqual(self.make_tags(raw_tags).artists, tags.artists)
|
||||
|
||||
def test_decade(self):
|
||||
"""Test that the decade was tagged properly."""
|
||||
raw_tags = {"date": ["1988-06-17"]}
|
||||
tags = self.make_tags(raw_tags)
|
||||
self.assertIsInstance(tags.decade, emmental.db.decades.Decade)
|
||||
self.assertEqual(tags.decade.decade, 1980)
|
||||
|
||||
self.assertEqual(self.make_tags(raw_tags).decade, tags.decade)
|
||||
|
||||
def test_genres(self):
|
||||
"""Test that genres were tagged properly."""
|
||||
raw_tags = {"genre": ["Genre 1", "Genre 2"]}
|
||||
|
||||
tags = self.make_tags(raw_tags)
|
||||
self.assertEqual(len(tags.genres), 2)
|
||||
for i, genre in enumerate(tags.genres):
|
||||
with self.subTest(i=i):
|
||||
self.assertIsInstance(genre, emmental.db.genres.Genre)
|
||||
self.assertEqual(genre.name, f"Genre {i+1}")
|
||||
|
||||
self.assertListEqual(self.make_tags(raw_tags).genres, tags.genres)
|
||||
|
||||
def test_medium(self):
|
||||
"""Test that the medium was tagged properly."""
|
||||
raw_tags = {"album": ["Album Name"],
|
||||
"musicbrainz_releasegroupid": ["ab-cd-ef"],
|
||||
"albumartist": ["Album Artist"],
|
||||
"date": ["1988-06"],
|
||||
"discnumber": ["2"],
|
||||
"discsubtitle": ["Subtitle"],
|
||||
"media": ["CD"]}
|
||||
|
||||
tags = self.make_tags(raw_tags)
|
||||
self.assertIsInstance(tags.medium, emmental.db.media.Medium)
|
||||
self.assertEqual(tags.medium.number, 2)
|
||||
self.assertEqual(tags.medium.name, "Subtitle")
|
||||
self.assertEqual(tags.medium.type, "CD")
|
||||
|
||||
raw_tags["discsubtitle"] = ["New Subtitle"]
|
||||
self.assertEqual(self.make_tags(raw_tags).medium, tags.medium)
|
||||
self.assertEqual(tags.medium.name, "New Subtitle")
|
||||
|
||||
def test_year(self):
|
||||
"""Test that the year was tagged properly."""
|
||||
raw_tags = {"date": ["1988-06-17"]}
|
||||
tags = self.make_tags(raw_tags)
|
||||
self.assertIsInstance(tags.year, emmental.db.years.Year)
|
||||
self.assertEqual(tags.year.year, 1988)
|
||||
|
||||
self.assertEqual(self.make_tags(raw_tags).year, tags.year)
|
Loading…
Reference in New Issue