Skip to content

Commit

Permalink
more descriptions of how this works
Browse files Browse the repository at this point in the history
  • Loading branch information
azuline committed Oct 11, 2023
1 parent 1fcf376 commit 3680c60
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 13 deletions.
63 changes: 52 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ Rosé constructs the virtual filesystem from the audio tags. However, audio tags
are frequently missing or incorrect. Thus, Rosé also provides a set of tools to
improve the audio tag metadata.

Note that the metadata manager _modifies_ the

Which I have yet to write. Please check back later!

# Configuration
Expand All @@ -98,25 +100,64 @@ cache_dir = "~/.cache/rose"

The `--config/-c` flag overrides the config location.

## Library Conventions & Expectations
# Data Requirements

The `music_source_dir` must be a flat directory of albums, meaning all albums
must be top-level directories inside `music_source_dir`. Each album should also
be a single directory in `music_source_dir`.

Every directory should follow the format: `$music_source_dir/$album_name/$track.mp3`.

So for example: `$music_source_dir/BLACKPINK - 2016. SQUARE ONE/*.mp3`.

## Supported Filetypes

### Directory Structure
Rosé supports MP3, M4A, OGG, OPUS, and FLAC audio files and JPG and PNG image
files.

`$music_source_dir/albums/track.ogg`
## Tagging

### Supported Extensions
Rosé is somewhat lenient in the tags it ingests, but applies certain
conventions in the tags it writes.

### Tag Structure
Rosé uses the `;` character as a tag delimiter. For any tags where there are
multiple values, Rosé will write a single tag as a `;`-delimited string. For
example, `genre=Deep House;Techno`.

WIP
Rosé also writes the artists with a specific convention designed to indicate
the artist's role in a release. Rosé will write artist tags with the
delimiters: `;`, ` feat. `, ` pres.`, ` performed by `, and `remixed by` to
indicate the artist's role. So for example,
`artist=Pyotr Ilyich Tchaikovsky performed by André Previn;London Symphony Orchestra feat. Barack Obama`.

artist1;artist2 feat. artist3
An ambiguous BNF for the artist tag is:

BNF TODO
```
artist-tag ::= composer dj main guest remixer
composer ::= name ' performed by '
dj ::= name ' pres. '
main ::= name
guest ::= ' feat. ' name
remixer ::= ' remixed by ' name
name ::= name ';' | string
```

# Architecture

todo
Rosé has a simple uni-directional architecture. The source audio files are the
single source of truth. The read cache is transient and is solely populated by
changes made to the source audio files. The virtual filesystem is read-only and
uses the read cache for performance.

```mermaid
flowchart BT
A[Metadata Manager] -->|Maintains| B
B[Source Audio Files] -->|Populates| C
C[Read Cache] -->|Renders| D[Virtual Filesystem]
```

Rosé writes `.rose.{uuid}.toml` files into each album's directory as a way to
preserve state and keep release UUIDs consistent across cache rebuilds.

- db is read cache, not source of truth
- filetags and files are source of truth
Tracks are uniquely identified by the `(release_uuid, tracknumber, discnumber)`
tuple. If there is a 3-tuple collision, the track title is used to disambiguate.
9 changes: 7 additions & 2 deletions rose/artiststr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@ def _split_tag(t: str | None) -> list[str]:
li_composer = _split_tag(composer)
li_producer = _split_tag(producer)
li_dj = _split_tag(dj)
if main and "remixed by " in main:
main, remixer = re.split(r" ?remixed by ", main, maxsplit=1)
li_remixer.extend(_split_tag(remixer))
if main and "feat. " in main:
main, guests = re.split(r" ?feat. ", main, maxsplit=1)
li_guests.extend(_split_tag(guests))
if main and " pres. " in main:
if main and "pres. " in main:
dj, main = re.split(r" ?pres. ", main, maxsplit=1)
li_dj.extend(_split_tag(dj))
if main and " performed by " in main:
if main and "performed by " in main:
composer, main = re.split(r" ?performed by ", main, maxsplit=1)
li_composer.extend(_split_tag(composer))
if main:
Expand All @@ -62,4 +65,6 @@ def format_artist_string(a: Artists, genres: list[str]) -> str:
r = ";".join(a.djmixer) + " pres. " + r
if a.guest:
r += " feat. " + ";".join(a.guest)
if a.remixer:
r += " remixed by " + ";".join(a.remixer)
return r

0 comments on commit 3680c60

Please sign in to comment.