From 60f56ba3b721b4cba339c88f46a49f532070e0c7 Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Tue, 5 Mar 2019 15:52:07 -0500 Subject: [PATCH] curds: Add a Track Tag class This is similar to the Album tag and stores its data in the same dictionary. Signed-off-by: Anna Schumaker --- curds/tags.py | 31 ++++++++++++- curds/test_tags_track.py | 63 +++++++++++++++++++++++++++ curds/test_track.py | 94 ---------------------------------------- curds/track.py | 41 ------------------ 4 files changed, 93 insertions(+), 136 deletions(-) create mode 100644 curds/test_tags_track.py delete mode 100644 curds/test_track.py delete mode 100644 curds/track.py diff --git a/curds/tags.py b/curds/tags.py index 2adc0fb..842b5de 100644 --- a/curds/tags.py +++ b/curds/tags.py @@ -1,7 +1,10 @@ # Copyright 2019 (c) Anna Schumaker. +import mutagen +import os import re -tag_map = dict() +discno_map = { "one" : "1", "two" : "2", "three" : "3", "four" : "4", "five" : "5" } +tag_map = dict() class Tag: def extract(info, key, default): @@ -32,3 +35,29 @@ class Album(Tag): def lookup(info): return Tag.lookup(Album(info)) + +class Track(Tag): + def __init__(self, path): + info = mutagen.File(path) + + self.path = path + self.title = Tag.extract(info, "title", os.path.basename(path)) + self.artist = Tag.extract(info, "artist", "Unknown Artist") + self.tracknumber = int(Tag.extract(info, "tracknumber", 0)) + self.discnumber = int(Tag.extract(info, "discnumber", 1)) + self.length = info.info.length + self.album = Album.lookup(info) + + # Try to detect discnumbers that are embedded in the album name + discno = Tag.extract(info, "album", "Unknown Album")[len(self.album.album):] + match = re.search("([1-9][0-9]*)|one|two|three|four|five", discno.lower()) + if match: + self.discnumber = int(discno_map.get(match.group(), match.group())) + + def __hash__(self): + return hash((hash(self.album), self.title, self.artist, + self.tracknumber, self.discnumber)) + + def add(path): + track = Track(path) + return track if Tag.lookup(track) == track else None diff --git a/curds/test_tags_track.py b/curds/test_tags_track.py new file mode 100644 index 0000000..7a16c24 --- /dev/null +++ b/curds/test_tags_track.py @@ -0,0 +1,63 @@ +# Copyright 2019 (c) Anna Schumaker. +import os +import tags +import unittest + +test_tracks = os.path.abspath("./trier/Test Album") + +class TestTrackTag(unittest.TestCase): + def test_track_init_basic(self): + p = [ test_tracks, "01 - Test Track.ogg" ] + t = tags.Track(os.path.join(*p)) + self.assertIsInstance(t, tags.Tag) + self.assertEqual(t.path, os.path.join(*p)) + self.assertEqual(t.title, "Test Track") + self.assertEqual(t.artist, "Test Artist") + self.assertEqual(t.tracknumber, 1) + self.assertEqual(t.discnumber, 1) + self.assertEqual(t.length, 10) + self.assertEqual(t.album.album, "Test Album") + self.assertEqual(t.album.date, 2019) + self.assertEqual(t.album.genre, "Test") + self.assertEqual(t.album.albumartist, "Test Artist") + self.assertEqual(hash(t), hash((hash(t.album), "Test Track", "Test Artist", 1, 1))) + + def test_track_init_empty(self): + p = [ test_tracks, "00 - Empty Track.ogg" ] + t = tags.Track(os.path.join(*p)) + self.assertIsInstance(t, tags.Tag) + self.assertEqual(t.path, os.path.join(*p)) + self.assertEqual(t.title, "00 - Empty Track.ogg") + self.assertEqual(t.artist, "Unknown Artist") + self.assertEqual(t.tracknumber, 0) + self.assertEqual(t.length, 0) + self.assertEqual(t.discnumber, 1) + self.assertEqual(t.album.album, "Unknown Album") + self.assertEqual(t.album.date, 0) + self.assertEqual(t.album.genre, "Unknown") + self.assertEqual(t.album.albumartist, "Unknown Artist") + self.assertEqual(hash(t), hash((hash(t.album), "00 - Empty Track.ogg", "Unknown Artist", 0, 1))) + + def test_track_discno_detect(self): + join = os.path.join + self.assertEqual(tags.Track(join(test_tracks, "02 - Test {Disc 2}.ogg")).discnumber, 2) + self.assertEqual(tags.Track(join(test_tracks, "03 - Test [Disk One].ogg")).discnumber, 1) + self.assertEqual(tags.Track(join(test_tracks, "04 - Test (Disc Two).ogg")).discnumber, 2) + self.assertEqual(tags.Track(join(test_tracks, "05 - Test - Disc Three.ogg")).discnumber, 3) + self.assertEqual(tags.Track(join(test_tracks, "06 - Test;CD Four.ogg")).discnumber, 4) + self.assertEqual(tags.Track(join(test_tracks, "07 - Test;CdFive.ogg")).discnumber, 5) + self.assertEqual(tags.Track(join(test_tracks, "08 - Test CD 6/10.ogg")).discnumber, 6) + self.assertEqual(tags.Track(join(test_tracks, "09 - Test {Disc 02}.ogg")).discnumber, 2) + self.assertEqual(tags.Track(join(test_tracks, "10 - Test {Disc 20}.ogg")).discnumber, 20) + + def test_track_add(self): + tags.tag_map.clear() + t = tags.Track.add(os.path.join(test_tracks, "01 - Test Track.ogg")) + self.assertIsNotNone(t) + self.assertIn(t, tags.tag_map.values()) + self.assertIn(t.album, tags.tag_map.values()) + + for i in range(10): + u = tags.Track.add(os.path.join(test_tracks, "01 - Test Track.ogg")) + self.assertIsNone(u) + self.assertEqual(len(tags.tag_map), 2) diff --git a/curds/test_track.py b/curds/test_track.py deleted file mode 100644 index 6c32bf3..0000000 --- a/curds/test_track.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright 2019 (c) Anna Schumaker. -import concurrent.futures -import mutagen -import os -import track -import unittest - -test_tracks = os.path.abspath("./trier/Test Album") -test_library = os.path.abspath("./trier/Test Library") - -class TestTrackClass(unittest.TestCase): - def setUp(self): - track.track_list.clear() - - def test_init_basic(self): - p = [ test_tracks, "01 - Test Track.ogg" ] - t = track.Track(os.path.join(*p)) - self.assertEqual(t.path, os.path.join(*p)) - self.assertEqual(t.title, "Test Track") - self.assertEqual(t.artist, "Test Artist") - self.assertEqual(t.tracknumber, 1) - self.assertEqual(t.length, 10) - self.assertEqual(t.discnumber, 1) - self.assertEqual(t.album.album, "Test Album") - self.assertEqual(t.album.date, 2019) - self.assertEqual(t.album.genre, "Test") - self.assertEqual(t.album.albumartist, "Test Artist") - self.assertEqual(len(track.track_list), 0) - - def test_init_empty(self): - p = [ test_tracks, "00 - Empty Track.ogg" ] - t = track.Track(os.path.join(*p)) - self.assertEqual(t.path, os.path.join(*p)) - self.assertEqual(t.title, "00 - Empty Track.ogg") - self.assertEqual(t.artist, "Unknown Artist") - self.assertEqual(t.tracknumber , 0) - self.assertEqual(t.length, 0) - self.assertEqual(t.discnumber, 1) - self.assertEqual(t.album.album, "Unknown Album") - self.assertEqual(t.album.date, 0) - self.assertEqual(t.album.genre, "Unknown") - self.assertEqual(t.album.albumartist, "Unknown Artist") - self.assertEqual(len(track.track_list), 0) - - def test_init_discno_detect(self): - join = os.path.join - self.assertEqual(track.Track(join(test_tracks, "02 - Test {Disc 2}.ogg")).discnumber, 2) - self.assertEqual(track.Track(join(test_tracks, "03 - Test [Disk One].ogg")).discnumber, 1) - self.assertEqual(track.Track(join(test_tracks, "04 - Test (Disc Two).ogg")).discnumber, 2) - self.assertEqual(track.Track(join(test_tracks, "05 - Test - Disc Three.ogg")).discnumber, 3) - self.assertEqual(track.Track(join(test_tracks, "06 - Test;CD Four.ogg")).discnumber, 4) - self.assertEqual(track.Track(join(test_tracks, "07 - Test;CdFive.ogg")).discnumber, 5) - self.assertEqual(track.Track(join(test_tracks, "08 - Test CD 6/10.ogg")).discnumber, 6) - self.assertEqual(track.Track(join(test_tracks, "09 - Test {Disc 02}.ogg")).discnumber, 2) - self.assertEqual(track.Track(join(test_tracks, "10 - Test {Disc 20}.ogg")).discnumber, 20) - - def test_track_add(self): - fs = track.add(os.path.join(test_tracks, "01 - Test Track.ogg")) - self.assertEqual(len(track.track_list), 0) - t = fs.result() - self.assertIsNotNone(t) - self.assertEqual(len(track.track_list), 1) - self.assertIn(t, track.track_list) - - self.assertIsNone(track.add(os.path.join(test_tracks, "01 - Test Track.ogg")).result()) - self.assertEqual(len(track.track_list), 1) - self.assertIsNone(track.add("99 - No Such Track.ogg").result()) - self.assertEqual(len(track.track_list), 1) - - fs = track.add(os.path.join(test_tracks, "02 - Test {Disc 2}.ogg")) - u = fs.result() - self.assertIsNotNone(u) - self.assertEqual(len(track.track_list), 2) - self.assertIn(u, track.track_list) - - def test_parallel_add(self): - count = 0 - futures = [ ] - done = [ ] - none = [ ] - for dirname, subdirs, files in os.walk(test_library): - for f in files: - futures.append(track.add(os.path.join(dirname, f))) - futures.append(track.add(os.path.join(dirname, f))) - count += 1 - self.assertLess(len(track.track_list), count) - for fs in concurrent.futures.as_completed(futures): - if fs.result() is not None: - done.append(fs.result()) - else: - none.append(fs.result()) - self.assertEqual(len(done), count) - self.assertEqual(len(none), count) - self.assertEqual(len(track.track_list), count) diff --git a/curds/track.py b/curds/track.py deleted file mode 100644 index 6df790c..0000000 --- a/curds/track.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2019 (c) Anna Schumaker. -import tags -import concurrent.futures -import mutagen -import os -import re - -discno_map = { "one" : "1", "two" : "2", "three" : "3", "four" : "4", "five" : "5" } -track_list = [ ] -track_pool = concurrent.futures.ThreadPoolExecutor(max_workers = 1) - -class Track: - def __init__(self, path): - fileinfo = mutagen.File(path) - - self.path = path - self.title = fileinfo.get("title", [os.path.basename(path)])[0] - self.artist = fileinfo.get("artist", ["Unknown Artist"])[0] - self.tracknumber = int(fileinfo.get("tracknumber", ["0"])[0]) - self.length = fileinfo.info.length - self.album = tags.Album.lookup(fileinfo) - self.discnumber = int(fileinfo.get("discnumber", ["1"])[0]) - - # Try to detect discnumbers that are embedded in the album name - discno = fileinfo.get("album", ["Unknown Album"])[0][len(self.album.album):] - match = re.search("([1-9][0-9]*)|one|two|three|four|five", discno.lower()) - if match: - self.discnumber = int(discno_map.get(match.group(), match.group())) - - -def __add(path): - try: - if not any(t.path == path for t in track_list): - track = Track(path) - track_list.append(track) - return track - except mutagen.MutagenError: - return None - -def add(path): - return track_pool.submit(__add, path)