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 <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
04ac4398e2
commit
60f56ba3b7
|
@ -1,7 +1,10 @@
|
||||||
# Copyright 2019 (c) Anna Schumaker.
|
# Copyright 2019 (c) Anna Schumaker.
|
||||||
|
import mutagen
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
tag_map = dict()
|
discno_map = { "one" : "1", "two" : "2", "three" : "3", "four" : "4", "five" : "5" }
|
||||||
|
tag_map = dict()
|
||||||
|
|
||||||
class Tag:
|
class Tag:
|
||||||
def extract(info, key, default):
|
def extract(info, key, default):
|
||||||
|
@ -32,3 +35,29 @@ class Album(Tag):
|
||||||
def lookup(info):
|
def lookup(info):
|
||||||
return Tag.lookup(Album(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
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
|
|
@ -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)
|
|
Loading…
Reference in New Issue