diff --git a/polyphonic/library/models.py b/polyphonic/library/models.py index 26d2e33..595e85a 100644 --- a/polyphonic/library/models.py +++ b/polyphonic/library/models.py @@ -292,7 +292,11 @@ class Work(models.Model): @property def digital_parts(self): - sections = [(s.tag, s.name) for s in Section.objects.filter(doc__work=self.pk)] + sections = [ + (s.tag, s.name) + for s in Section.objects.filter(doc__work=self.pk) + if ":" not in s.tag + ] sections.sort(key=self.orchestration.sorter()) # return [ s[1] for s in sections ] sections = list(dict(sections).items()) # primitive unique() diff --git a/polyphonic/library/music_tags.py b/polyphonic/library/music_tags.py index c35094f..cba866c 100644 --- a/polyphonic/library/music_tags.py +++ b/polyphonic/library/music_tags.py @@ -1,14 +1,15 @@ -from collections import namedtuple +from dataclasses import dataclass import re -GENERAL = """ -mvmt Movement -ex Excerpt -sect Section -pce Piece -no No. -page Page -""" + +TAG_PREFIXES = { + "mvmt": "Movement", + "ex": "Excerpt", + "sect": "Section", + "name": "Name", + "no": "Number", + "page": "Page", +} # taken from https://imslp.org/wiki/IMSLP:Abbreviations_for_MusicTags # Include any aliases at the top @@ -160,71 +161,110 @@ xyl Xylophone zith Zither """ -MUSIC_TAGS = [] -GENERAL_TAGS = set() -for i, abbreviations in enumerate((GENERAL, INSTRUMENTS)): - for line in abbreviations.split("\n"): - parts = line.strip().split(maxsplit=1) - if len(parts) < 2: - continue - name, _, _ = parts[1].partition("(") - MUSIC_TAGS.append((parts[0], name)) - if i == 0: - GENERAL_TAGS.add(parts[0]) +MUSIC_TAG = re.compile(r"((?P\w+):)?(?P.*?)(\-(?P[0-9]+))?") +MUSIC_TAGS = [] +TAG_ALIASES = {} +# GENERAL_TAGS = set() +for line in INSTRUMENTS.split("\n"): + parts = line.strip().split(maxsplit=1) + if len(parts) < 2: + continue + name, _, _ = parts[1].partition("(") + MUSIC_TAGS.append((parts[0], name.strip())) + # if i == 0: + # GENERAL_TAGS.add(parts[0]) + TAG_ALIASES.setdefault(name, []).append(parts[0]) MUSIC_NAME_BY_TAG = dict(MUSIC_TAGS) MUSIC_TAG_BY_NAME = dict(((x[1].lower(), x[0]) for x in MUSIC_TAGS)) -class MusicTag(namedtuple("MusicTag", ("name", "variant"), defaults=[None])): +def slug(s): + """ + >>> slug("This is a test") + 'This_is_a_test' + """ + return str(s).replace(" ", "_") + + +def deslug(s): + return s.replace("_", " ") + + +@dataclass +class MusicTag: + name: str + number: int | None = None + prefix: str = "" + @classmethod def from_tag(cls, tag): """ >>> MusicTag.from_tag('vn-1') - MusicTag(name='Violin', variant='1') + MusicTag(name='Violin', number=1, prefix='') >>> MusicTag.from_tag('db') - MusicTag(name='Double Bass', variant=None) + MusicTag(name='Double Bass', number=None, prefix='') >>> MusicTag.from_tag('Jaws Harp') - MusicTag(name='Jaws Harp', variant=None) - >>> MusicTag.from_tag('mvmt-2') - MusicTag(name='Movement', variant='2') - >>> MusicTag.from_tag('pce-A2') - MusicTag(name='Piece', variant='A2') + MusicTag(name='Jaws Harp', number=None, prefix='') + >>> MusicTag.from_tag('mvmt:Largo-2') + MusicTag(name='Largo', number=2, prefix='mvmt') + >>> MusicTag.from_tag('name:A2') + MusicTag(name='A2', number=None, prefix='name') + >>> MusicTag.from_tag('name:Ode_to_Joy') + MusicTag(name='Ode to Joy', number=None, prefix='name') + >>> MusicTag.from_tag('no:-2') + MusicTag(name='', number=2, prefix='no') """ - abbr, _, variant = tag.partition("-") - name = MUSIC_NAME_BY_TAG.get(abbr.lower(), abbr) - if variant: - return cls(name, variant) - return cls(name, None) + match = MUSIC_TAG.fullmatch(tag) - @property - def tag(self): - lc = self.name.lower() - return MUSIC_TAG_BY_NAME.get(lc, lc) + if match is None: + raise ValueError("Not a valid tag") + + result = match.groupdict() + + if result["prefix"] is None: + result["name"] = MUSIC_NAME_BY_TAG.get( + result["name"].lower(), deslug(result["name"]) + ) + result["prefix"] = "" + else: + result["name"] = deslug(result["name"]) + + result["number"] = int(result["number"]) if result["number"] else None + + return cls(**result) @property def is_general(self): """ - >>> MusicTag('Piece', 'A3').is_general + >>> MusicTag('A3', prefix="name").is_general True >>> MusicTag('Violin', 2).is_general False """ - return self.tag in GENERAL_TAGS + return self.prefix in TAG_PREFIXES - def abbreviate(self): + @property + def tag(self): """ - >>> MusicTag('Violin', 1).abbreviate() + >>> MusicTag('Violin', 1).tag 'vn-1' - >>> MusicTag('Double Bass').abbreviate() + >>> MusicTag('Double Bass').tag 'db' + >>> MusicTag('left', prefix="page:").tag + 'page:left' + >>> MusicTag("Ode to Joy", prefix="name:").tag + 'name:Ode_to_Joy' + >>> MusicTag('Unknown Instrument').tag + 'Unknown_Instrument' """ - tag = MUSIC_TAG_BY_NAME.get(self.name.lower()) - if self.variant: - tag = f"{tag}-{self.variant}" - return tag + parts = [self.prefix] + parts.append(MUSIC_TAG_BY_NAME.get(self.name.lower(), slug(self.name))) + if self.number: + parts.extend(["-", str(self.number)]) + return "".join(parts) def __str__(self): """ @@ -232,15 +272,17 @@ class MusicTag(namedtuple("MusicTag", ("name", "variant"), defaults=[None])): 'Violin 1' >>> str(MusicTag('Double Bass')) 'Double Bass' + >>> str(MusicTag('Unknown Instrument')) + 'Unknown Instrument' """ - if self.variant: - return f"{self.name} {self.variant}" + if self.number: + return f"{self.name} {self.number}" return self.name PATTERNS = [ - re.compile(r"(?P[A-Za-z]+)[_\- ]*(?P\d+)"), - re.compile(r"(?P\d+)(st|nd|rd|th)[_\- ]*(?P[A-Za-z]+)"), + re.compile(r"(?P[A-Za-z]+)[_\- ]*(?P\d+)"), + re.compile(r"(?P\d+)(st|nd|rd|th)[_\- ]*(?P[A-Za-z]+)"), re.compile(r"(?P[A-Za-z]+)()"), ] @@ -248,34 +290,33 @@ PATTERNS = [ def auto_tag(filename): """ - >>> auto_tag('Ode to Joy - Violin 1.pdf') - MusicTag(name='Violin', variant=1) - >>> auto_tag('Ode to Joy_Cello.pdf') - MusicTag(name='Cello', variant=None) - >>> auto_tag('Ode to Joy violin - 1.pdf') - MusicTag(name='Violin', variant=1) - >>> auto_tag('Ode to Joy - vla.pdf') - MusicTag(name='Viola', variant=None) - >>> auto_tag('Ode to Joy - fl-2 (piccolo).pdf') - MusicTag(name='Flute', variant=2) - >>> auto_tag('1st Violin - Ode to Joy.pdf') - MusicTag(name='Violin', variant=1) - >>> auto_tag('Ode to Joy - 2nd Violin.pdf') - MusicTag(name='Violin', variant=2) - + >>> auto_tag('Ode to Joy - Violin 1.pdf').tag + 'vn-1' + >>> auto_tag('Ode to Joy_Cello.pdf').tag + 'vc' + >>> auto_tag('Ode to Joy violin - 1.pdf').tag + 'vn-1' + >>> auto_tag('Ode to Joy - vla.pdf').tag + 'va' + >>> auto_tag('Ode to Joy - fl-2 (piccolo).pdf').tag + 'fl-2' + >>> auto_tag('1st Violin - Ode to Joy.pdf').tag + 'vn-1' + >>> auto_tag('Ode to Joy - 2nd Violin.pdf').tag + 'vn-2' """ for pattern in PATTERNS: for m in pattern.finditer(filename): inst = m["inst"].lower() try: - ordinal = int(m["ord"]) + number = int(m["number"]) except IndexError: - ordinal = None + number = None if inst in MUSIC_TAG_BY_NAME: - return MusicTag(inst.title(), ordinal) + return MusicTag(inst.title(), number) if inst in MUSIC_NAME_BY_TAG: - return MusicTag(MUSIC_NAME_BY_TAG[inst], ordinal) + return MusicTag(MUSIC_NAME_BY_TAG[inst], number) if __name__ == "__main__": diff --git a/polyphonic/library/templates/library/document_annotate.html b/polyphonic/library/templates/library/document_annotate.html index 740abf1..9a20f71 100644 --- a/polyphonic/library/templates/library/document_annotate.html +++ b/polyphonic/library/templates/library/document_annotate.html @@ -80,6 +80,7 @@ .tag-name { flex: 1; text-align: right; + cursor: pointer; } .tag-handle-resize { position: absolute; @@ -140,7 +141,7 @@

Actions

- New Tag + New Tag
@@ -163,18 +164,19 @@ -