Skip to content

Commit

Permalink
write the rose release_id to the audio tags too
Browse files Browse the repository at this point in the history
  • Loading branch information
azuline committed Oct 29, 2023
1 parent d34dcde commit 87715cb
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 47 deletions.
8 changes: 5 additions & 3 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ These UUIDs are persisted to the source files:
- Each release has a `.rose.{uuid}.toml` file, which preserves release-level
state, such as `New`. The UUID is in the filename instead of the file
contents for improved performance: we can collect the UUID via a `readdir`
call instead of an expensive file read.
- Each track has a custom `roseid` tag. This tag is written to the source audio
file.
call instead of an expensive file read. The release UUID is also written to
the nonstandard `rosereleaseid` audio tag for increased robustness to partial
data loss and race conditions.
- Each track has a nonstandard `roseid` audio tag that contains the track UUID.
This tag is written to the source audio file.

Therefore, provided that other programs do not erase the UUID, Rosé will be
able to identify releases and tracks across arbitrarily drastic directory and
Expand Down
82 changes: 43 additions & 39 deletions docs/METADATA_MANAGEMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Rosé manages the following tags:
- Track Number
- Disc Number
- Rosé ID
- Rosé Release ID

Rosé does not care about any other tags and does not do anything with them.

Expand Down Expand Up @@ -205,48 +206,51 @@ from additional fields.

## MP3

| Tag | Field Name | Will Ingest These Fields |
| ------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| Release Title | `TALB` | |
| Album Artists | `TPE2` | |
| Release Year | `TDRC` | `TYER` |
| Release Type | `TXXX:RELEASETYPE` | |
| Genre | `TCON` | |
| Label | `TPUB` | |
| Track Title | `TIT2` | |
| Track Artists | `TPE1` | `TPE4` (Remixer), `TCOM` (Composer), `TPE3` (Conductor), `TIPL,IPLS/producer` (producer), `TIPL,IPLS/DJ-mix` (djmixer) |
| Track Number | `TRCK` | |
| Disc Number | `TPOS` | |
| Rose ID | `TXXX:ROSEID` | |
| Tag | Field Name | Will Ingest These Fields |
| --------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Release Title | `TALB` | |
| Album Artists | `TPE2` | |
| Release Year | `TDRC` | `TYER` |
| Release Type | `TXXX:RELEASETYPE` | |
| Genre | `TCON` | |
| Label | `TPUB` | |
| Track Title | `TIT2` | |
| Track Artists | `TPE1` | `TPE4` (Remixer), `TCOM` (Composer), `TPE3` (Conductor), `TIPL,IPLS/producer` (producer), `TIPL,IPLS/DJ-mix` (djmixer) |
| Track Number | `TRCK` | |
| Disc Number | `TPOS` | |
| Rosé ID | `TXXX:ROSEID` | |
| Rosé Release ID | `TXXX:ROSERELEASEID` | |

## MP4

| Tag | Field Name | Will Ingest These Fields |
| ------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Release Title | `\xa9alb` | |
| Album Artists | `aART` | |
| Release Year | `\xa9day` | |
| Release Type | `----:com.apple.iTunes:RELEASETYPE` | |
| Genre | `\xa9gen` | |
| Label | `----:com.apple.iTunes:LABEL` | |
| Track Title | `\xa9nam` | |
| Track Artists | `\xa9ART` | `----:com.apple.iTunes:REMIXER` (Remixer), `\xa9wrt` (Composer), `----:com.apple.iTunes:CONDUCTOR` (Conductor), `----:com.apple.iTunes:PRODUCER` (producer), `----:com.apple.iTunes:DJMIXER` (djmixer) |
| Track Number | `trkn` | |
| Disc Number | `disk` | |
| Rose ID | `----:net.sunsetglow.rose:ID` | |
| Tag | Field Name | Will Ingest These Fields |
| --------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Release Title | `\xa9alb` | |
| Album Artists | `aART` | |
| Release Year | `\xa9day` | |
| Release Type | `----:com.apple.iTunes:RELEASETYPE` | |
| Genre | `\xa9gen` | |
| Label | `----:com.apple.iTunes:LABEL` | |
| Track Title | `\xa9nam` | |
| Track Artists | `\xa9ART` | `----:com.apple.iTunes:REMIXER` (Remixer), `\xa9wrt` (Composer), `----:com.apple.iTunes:CONDUCTOR` (Conductor), `----:com.apple.iTunes:PRODUCER` (producer), `----:com.apple.iTunes:DJMIXER` (djmixer) |
| Track Number | `trkn` | |
| Disc Number | `disk` | |
| Rosé ID | `----:net.sunsetglow.rose:ID` | |
| Rosé Release ID | `----:net.sunsetglow.rose:RELEASEID` | |

## Vorbis

| Tag | Field Name | Will Ingest These Fields |
| ------------- | -------------- | --------------------------------------------------------------------------------------------------------------- |
| Release Title | `album` | |
| Album Artists | `albumartist` | |
| Release Year | `date` | `year` |
| Release Type | `releasetype` | |
| Genre | `genre` | |
| Label | `organization` | `label`, `recordlabel` |
| Track Title | `title` | |
| Track Artists | `artist` | `remixer` (Remixer), `composer` (Composer), `conductor` (Conductor), `producer` (producer), `djmixer` (djmixer) |
| Track Number | `tracknumber` | |
| Disc Number | `discnumber` | |
| Rose ID | `roseid` | |
| Tag | Field Name | Will Ingest These Fields |
| --------------- | --------------- | --------------------------------------------------------------------------------------------------------------- |
| Release Title | `album` | |
| Album Artists | `albumartist` | |
| Release Year | `date` | `year` |
| Release Type | `releasetype` | |
| Genre | `genre` | |
| Label | `organization` | `label`, `recordlabel` |
| Track Title | `title` | |
| Track Artists | `artist` | `remixer` (Remixer), `composer` (Composer), `conductor` (Conductor), `producer` (producer), `djmixer` (djmixer) |
| Track Number | `tracknumber` | |
| Disc Number | `discnumber` | |
| Rosé ID | `roseid` | |
| Rosé Release ID | `rosereleaseid` | |
11 changes: 8 additions & 3 deletions rose/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -812,13 +812,18 @@ def _update_cache_for_releases_executor(
# so, since this occurs once over the lifetime of the track's existence in Rose. We
# optimize this function because it is called repeatedly upon every metadata edit, but
# in this case, we skip this code path once an ID is generated.
#
# We also write the release ID to the tags. This is not needed in normal operations
# (since we have .rose.{uuid}.toml!), but provides a layer of defense in situations like
# a directory being written file-by-file and being processed in a half-written state.
track_id = tags.id
if not track_id:
if not track_id or not tags.release_id:
with lock(c, release_lock_name(release.id)):
track_id = str(uuid6.uuid7())
tags.id = track_id
tags.id = tags.id or str(uuid6.uuid7())
tags.release_id = release.id
tags.flush()
# And refresh the mtime because we've just written to the file.
track_id = tags.id
track_mtime = str(os.stat(f).st_mtime)

# And now create the cached track.
Expand Down
27 changes: 25 additions & 2 deletions rose/cache_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
)
from rose.common import VERSION
from rose.config import Config
from rose.tagger import AudioFile


def test_schema(config: Config) -> None:
Expand Down Expand Up @@ -272,8 +273,8 @@ def test_update_cache_releases_uncached_with_existing_id(config: Config) -> None

def test_update_cache_releases_preserves_track_ids_across_rebuilds(config: Config) -> None:
"""Test that track IDs are preserved across cache rebuilds."""
release_dir = config.music_source_dir / TEST_RELEASE_2.name
shutil.copytree(TEST_RELEASE_2, release_dir)
release_dir = config.music_source_dir / TEST_RELEASE_3.name
shutil.copytree(TEST_RELEASE_3, release_dir)
update_cache_for_releases(config, [release_dir])
with connect(config) as conn:
cursor = conn.execute("SELECT id FROM tracks")
Expand All @@ -293,6 +294,28 @@ def test_update_cache_releases_preserves_track_ids_across_rebuilds(config: Confi
assert first_track_ids == second_track_ids


def test_update_cache_releases_writes_ids_to_tags(config: Config) -> None:
"""Test that track IDs and release IDs are written to files."""
release_dir = config.music_source_dir / TEST_RELEASE_3.name
shutil.copytree(TEST_RELEASE_3, release_dir)

af = AudioFile.from_file(release_dir / "01.m4a")
assert af.id is None
assert af.release_id is None
af = AudioFile.from_file(release_dir / "02.m4a")
assert af.id is None
assert af.release_id is None

update_cache_for_releases(config, [release_dir])

af = AudioFile.from_file(release_dir / "01.m4a")
assert af.id is not None
assert af.release_id is not None
af = AudioFile.from_file(release_dir / "02.m4a")
assert af.id is not None
assert af.release_id is not None


def test_update_cache_releases_already_fully_cached(config: Config) -> None:
"""Test that a fully cached release No Ops when updated again."""
release_dir = config.music_source_dir / TEST_RELEASE_1.name
Expand Down
Loading

0 comments on commit 87715cb

Please sign in to comment.