Skip to content

Commit

Permalink
Add compositionyear and catalognumber tags (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
azuline authored Apr 20, 2024
1 parent 8030486 commit e0b1492
Show file tree
Hide file tree
Showing 28 changed files with 353 additions and 45 deletions.
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,6 @@ Rosé's CLI is also designed to make scripting against your library easy. Operat
release" and "jump to artist" can be expressed as a bash one-liner and integrated into your file
manager.

See [Shell Scripting](./docs/SHELL_SCRIPTING.md) for additional documentation on scripting with
Rosé.

# Learn More

For additional documentation, please refer to the following files:
Expand Down
12 changes: 8 additions & 4 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@ def seeded_cache(config: Config) -> None:
conn.executescript(
f"""\
INSERT INTO releases
(id , source_path , cover_image_path , added_at , datafile_mtime, title , releasetype, releaseyear, disctotal, new , metahash)
VALUES ('r1', '{dirpaths[0]}', null , '0000-01-01T00:00:00+00:00', '999' , 'Release 1', 'album' , 2023 , 1 , false, '1')
, ('r2', '{dirpaths[1]}', '{imagepaths[0]}', '0000-01-01T00:00:00+00:00', '999' , 'Release 2', 'album' , 2021 , 1 , false, '2')
, ('r3', '{dirpaths[2]}', null , '0000-01-01T00:00:00+00:00', '999' , 'Release 3', 'album' , 2021 , 1 , true , '3');
(id , source_path , cover_image_path , added_at , datafile_mtime, title , releasetype, releaseyear, compositionyear, catalognumber, disctotal, new , metahash)
VALUES ('r1', '{dirpaths[0]}', null , '0000-01-01T00:00:00+00:00', '999' , 'Release 1', 'album' , 2023 , null , null , 1 , false, '1')
, ('r2', '{dirpaths[1]}', '{imagepaths[0]}', '0000-01-01T00:00:00+00:00', '999' , 'Release 2', 'album' , 2021 , null , 'DG-001' , 1 , false, '2')
, ('r3', '{dirpaths[2]}', null , '0000-01-01T00:00:00+00:00', '999' , 'Release 3', 'album' , 2021 , 1780 , 'DG-002' , 1 , true , '3');
INSERT INTO releases_genres
(release_id, genre , genre_sanitized, position)
Expand Down Expand Up @@ -184,6 +184,8 @@ def seeded_cache(config: Config) -> None:
, discnumber
, releasetitle
, releaseyear
, compositionyear
, catalognumber
, releasetype
, genre
, label
Expand All @@ -197,6 +199,8 @@ def seeded_cache(config: Config) -> None:
, process_string_for_fts(t.discnumber) AS discnumber
, process_string_for_fts(r.title) AS releasetitle
, process_string_for_fts(r.releaseyear) AS releaseyear
, process_string_for_fts(r.compositionyear) AS compositionyear
, process_string_for_fts(r.catalognumber) AS catalognumber
, process_string_for_fts(r.releasetype) AS releasetype
, process_string_for_fts(COALESCE(GROUP_CONCAT(rg.genre, ' '), '')) AS genre
, process_string_for_fts(COALESCE(GROUP_CONCAT(rl.label, ' '), '')) AS label
Expand Down
4 changes: 4 additions & 0 deletions docs/METADATA_TOOLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ title = "Mix & Match"
new = false
releasetype = "ep"
releaseyear = 2017
compositionyear = -9999
genres = [
"Dance-Pop",
"Future Bass",
Expand All @@ -48,6 +49,7 @@ genres = [
labels = [
"BlockBerry Creative",
]
catalognumber = "WMED0709"
artists = [
{ name = "LOOΠΔ ODD EYE CIRCLE", role = "main" },
]
Expand Down Expand Up @@ -295,8 +297,10 @@ The rules engine supports matching and acting on the following tags:
- `releaseartist[djmixer]`
- `releasetype`
- `releaseyear`
- `compositionyear`
- `genre`
- `label`
- `catalognumber`

The `trackartist[*]`, `releaseartist[*]`, `genre`, and `label` tags are _multi-value_ tags, which
have a slightly different behavior from single-value tags for some of the actions. We'll explore
Expand Down
1 change: 0 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ For more detailed documentation, please visit the following files:
- [Improving Your Music Metadata](./METADATA_TOOLS.md)
- [Maintaining the Cache](./CACHE_MAINTENANCE.md)
- [Directory & Filename Templates](./TEMPLATES.md)
- [Shell Scripting](./SHELL_SCRIPTING.md)
- [Tagging Conventions](./docs/TAGGING_CONVENTIONS.md)
- [Architecture](./ARCHITECTURE.md)

Expand Down
3 changes: 0 additions & 3 deletions docs/SHELL_SCRIPTING.md

This file was deleted.

8 changes: 6 additions & 2 deletions docs/TEMPLATES.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,14 @@ Rosé provides the following template variables for releases:
```python
added_at: str # ISO8601 timestamp of when the release was added to the library.
releasetitle: str
releasetype: str # Type of the release (e.g. single, ep, etc). One of the enums as defined in TAGGING_CONVENTIONS.md.
releasetype: str # The type of the release (e.g. single, ep, etc). One of the enums as defined in TAGGING_CONVENTIONS.md.
releaseyear: int | None
compositionyear: int | None # The year that the release was composed. Mainly of interest in classical music.
new: bool # The "new"-ness of the release. See RELEASES.md for documentation on this feature.
disctotal: int # The number of discs in the release.
genres: list[str]
labels: list[str]
catalognumber: str | None
releaseartists: ArtistMapping # All release artists: an object with 6 properties, each corresponding to one role.
releaseartists.main: list[Artist] # The Artist object has a `name` property with the artist name.
releaseartists.guest: list[Artist]
Expand Down Expand Up @@ -113,11 +115,13 @@ trackartists.composer: list[Artist]
trackartists.conductor: list[Artist]
trackartists.djmixer: list[Artist]
releasetitle: str
releasetype: str # Type of the track's release (e.g. single, ep, etc).
releasetype: str # The type of the track's release (e.g. single, ep, etc).
releaseyear: int | None
compositionyear: int | None # The year that the release was composed. Mainly of interest in classical music.
new: bool # The "new"-ness of the track's release.
genres: list[str]
labels: list[str]
catalognumber: str | None
releaseartists: ArtistMapping # All release artists: an object with 6 properties, each corresponding to one role.
releaseartists.main: list[Artist] # The Artist object has a `name` property with the artist name.
releaseartists.guest: list[Artist]
Expand Down
32 changes: 25 additions & 7 deletions rose/audiotags.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,15 @@ class AudioTags:
release_id: str | None
title: str | None
releaseyear: int | None
compositionyear: int | None
tracknumber: str | None
tracktotal: int | None
discnumber: str | None
disctotal: int | None
release: str | None
genre: list[str]
label: list[str]
catalognumber: str | None
releasetype: str

releaseartists: ArtistMapping
Expand Down Expand Up @@ -140,17 +142,19 @@ def _get_paired_frame(x: str) -> str | None:
return None

return AudioTags(
id=_get_tag(m.tags, ["TXXX:ROSEID"]),
release_id=_get_tag(m.tags, ["TXXX:ROSERELEASEID"]),
id=_get_tag(m.tags, ["TXXX:ROSEID"], first=True),
release_id=_get_tag(m.tags, ["TXXX:ROSERELEASEID"], first=True),
title=_get_tag(m.tags, ["TIT2"]),
releaseyear=_parse_year(_get_tag(m.tags, ["TDRC", "TYER"])),
compositionyear=_parse_year(_get_tag(m.tags, ["TXXX:COMPOSITIONDATE"], first=True)),
tracknumber=tracknumber,
tracktotal=tracktotal,
discnumber=discnumber,
disctotal=disctotal,
release=_get_tag(m.tags, ["TALB"]),
genre=_split_tag(_get_tag(m.tags, ["TCON"], split=True)),
label=_split_tag(_get_tag(m.tags, ["TPUB"], split=True)),
catalognumber=_get_tag(m.tags, ["TXXX:CATALOGNUMBER"], first=True),
releasetype=_normalize_rtype(_get_tag(m.tags, ["TXXX:RELEASETYPE"], first=True)),
releaseartists=parse_artist_string(main=_get_tag(m.tags, ["TPE2"], split=True)),
trackartists=parse_artist_string(
Expand Down Expand Up @@ -178,13 +182,17 @@ def _get_paired_frame(x: str) -> str | None:
release_id=_get_tag(m.tags, ["----:net.sunsetglow.rose:RELEASEID"]),
title=_get_tag(m.tags, ["\xa9nam"]),
releaseyear=_parse_year(_get_tag(m.tags, ["\xa9day"])),
compositionyear=_parse_year(
_get_tag(m.tags, ["----:net.sunsetglow.rose:COMPOSITIONDATE"])
),
tracknumber=str(tracknumber),
tracktotal=tracktotal,
discnumber=str(discnumber),
disctotal=disctotal,
release=_get_tag(m.tags, ["\xa9alb"]),
genre=_split_tag(_get_tag(m.tags, ["\xa9gen"], split=True)),
label=_split_tag(_get_tag(m.tags, ["----:com.apple.iTunes:LABEL"], split=True)),
catalognumber=_get_tag(m.tags, ["----:com.apple.iTunes:CATALOGNUMBER"]),
releasetype=_normalize_rtype(
_get_tag(m.tags, ["----:com.apple.iTunes:RELEASETYPE"], first=True)
),
Expand All @@ -206,15 +214,17 @@ def _get_paired_frame(x: str) -> str | None:
release_id=_get_tag(m.tags, ["rosereleaseid"]),
title=_get_tag(m.tags, ["title"]),
releaseyear=_parse_year(_get_tag(m.tags, ["date", "year"])),
compositionyear=_parse_year(_get_tag(m.tags, ["compositiondate"])),
tracknumber=_get_tag(m.tags, ["tracknumber"], first=True),
tracktotal=_parse_int(_get_tag(m.tags, ["tracktotal"], first=True)),
discnumber=_get_tag(m.tags, ["discnumber"], first=True),
disctotal=_parse_int(_get_tag(m.tags, ["disctotal"], first=True)),
release=_get_tag(m.tags, ["album"]),
genre=_split_tag(_get_tag(m.tags, ["genre"], split=True)),
label=_split_tag(
_get_tag(m.tags, ["organization", "label", "recordlabel"], split=True)
_get_tag(m.tags, ["label", "organization", "recordlabel"], split=True)
),
catalognumber=_get_tag(m.tags, ["catalognumber"]),
releasetype=_normalize_rtype(_get_tag(m.tags, ["releasetype"], first=True)),
releaseartists=parse_artist_string(
main=_get_tag(m.tags, ["albumartist"], split=True)
Expand Down Expand Up @@ -263,7 +273,7 @@ def _write_tag_with_description(name: str, value: str | None) -> None:
keep_fields = [f for f in m.tags.getall(key) if getattr(f, "desc", None) != desc]
m.tags.delall(key)
if value:
frame = getattr(mutagen.id3, key)(desc=desc, text=value)
frame = getattr(mutagen.id3, key)(desc=desc, text=[value])
m.tags.add(frame)
for f in keep_fields:
m.tags.add(f)
Expand All @@ -272,11 +282,13 @@ def _write_tag_with_description(name: str, value: str | None) -> None:
_write_tag_with_description("TXXX:ROSERELEASEID", self.release_id)
_write_standard_tag("TIT2", self.title)
_write_standard_tag("TDRC", str(self.releaseyear).zfill(4))
_write_tag_with_description("TXXX:COMPOSITIONDATE", self.compositionyear)
_write_standard_tag("TRCK", self.tracknumber)
_write_standard_tag("TPOS", self.discnumber)
_write_standard_tag("TALB", self.release)
_write_standard_tag("TCON", ";".join(self.genre))
_write_standard_tag("TPUB", ";".join(self.label))
_write_tag_with_description("TXXX:CATALOGNUMBER", self.catalognumber)
_write_tag_with_description("TXXX:RELEASETYPE", self.releasetype)
_write_standard_tag("TPE2", format_artist_string(self.releaseartists))
_write_standard_tag("TPE1", format_artist_string(self.trackartists))
Expand All @@ -297,9 +309,13 @@ def _write_tag_with_description(name: str, value: str | None) -> None:
m.tags["----:net.sunsetglow.rose:RELEASEID"] = (self.release_id or "").encode()
m.tags["\xa9nam"] = self.title or ""
m.tags["\xa9day"] = str(self.releaseyear).zfill(4)
m.tags["----:net.sunsetglow.rose:COMPOSITIONDATE"] = (
str(self.compositionyear).zfill(4).encode()
)
m.tags["\xa9alb"] = self.release or ""
m.tags["\xa9gen"] = ";".join(self.genre)
m.tags["----:com.apple.iTunes:LABEL"] = ";".join(self.label).encode()
m.tags["----:com.apple.iTunes:CATALOGNUMBER"] = (self.catalognumber or "").encode()
m.tags["----:com.apple.iTunes:RELEASETYPE"] = self.releasetype.encode()
m.tags["aART"] = format_artist_string(self.releaseartists)
m.tags["\xa9ART"] = format_artist_string(self.trackartists)
Expand Down Expand Up @@ -351,11 +367,13 @@ def _write_tag_with_description(name: str, value: str | None) -> None:
m.tags["rosereleaseid"] = self.release_id or ""
m.tags["title"] = self.title or ""
m.tags["date"] = str(self.releaseyear).zfill(4)
m.tags["compositiondate"] = str(self.compositionyear).zfill(4)
m.tags["tracknumber"] = self.tracknumber or ""
m.tags["discnumber"] = self.discnumber or ""
m.tags["album"] = self.release or ""
m.tags["genre"] = ";".join(self.genre)
m.tags["organization"] = ";".join(self.label)
m.tags["label"] = ";".join(self.label)
m.tags["catalognumber"] = self.catalognumber or ""
m.tags["releasetype"] = self.releasetype
m.tags["albumartist"] = format_artist_string(self.releaseartists)
m.tags["artist"] = format_artist_string(self.trackartists)
Expand Down Expand Up @@ -399,8 +417,8 @@ def _get_tag(t: Any, keys: list[str], *, split: bool = False, first: bool = Fals
f"Encountered a tag value of type {type(val)}"
)
if first:
return values[0] if values else None
return r" \\ ".join(values)
return (values[0] or None) if values else None
return r" \\ ".join(values) or None
except (KeyError, ValueError):
pass
return None
Expand Down
29 changes: 16 additions & 13 deletions rose/audiotags_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,21 @@
)
def test_getters(filename: str, track_num: str, duration: int) -> None:
af = AudioTags.from_file(TEST_TAGGER / filename)
assert af.title == f"Track {track_num}"

assert af.tracknumber == track_num
assert af.tracktotal == 5
assert af.discnumber == "1"
assert af.disctotal == 1

assert af.release == "A Cool Album"
assert af.releasetype == "album"
assert af.releaseyear == 1990
assert af.compositionyear == 1984
assert af.genre == ["Electronic", "House"]
assert af.label == ["A Cool Label"]

assert af.catalognumber == "DN-420"
assert af.releaseartists.main == [Artist("Artist A"), Artist("Artist B")]

assert af.tracknumber == track_num
assert af.tracktotal == 5
assert af.discnumber == "1"
assert af.disctotal == 1

assert af.title == f"Track {track_num}"
assert af.trackartists == ArtistMapping(
main=[Artist("Artist A"), Artist("Artist B")],
guest=[Artist("Artist C"), Artist("Artist D")],
Expand Down Expand Up @@ -73,17 +74,19 @@ def test_flush(isolated_dir: Path, filename: str, track_num: str, duration: int)
af.flush()
af = AudioTags.from_file(fpath)

assert af.tracknumber == track_num
assert af.title == f"Track {track_num}"

assert af.release == "A Cool Album"
assert af.releasetype == "album"
assert af.releaseyear == 1990
assert af.discnumber == "1"
assert af.compositionyear == 1984
assert af.genre == ["Electronic", "House"]
assert af.label == ["A Cool Label"]

assert af.catalognumber == "DN-420"
assert af.releaseartists.main == [Artist("Artist A"), Artist("Artist B")]

assert af.tracknumber == track_num
assert af.discnumber == "1"

assert af.title == f"Track {track_num}"
assert af.trackartists == ArtistMapping(
main=[Artist("Artist A"), Artist("Artist B")],
guest=[Artist("Artist C"), Artist("Artist D")],
Expand Down
Loading

0 comments on commit e0b1492

Please sign in to comment.