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 5e94f89
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 15 deletions.
67 changes: 54 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A virtual filesystem music library and a music metadata manager.

Rosé reads a source directory of albums like this:

```
```tree
.
├── BLACKPINK - 2016. SQUARE ONE
├── BLACKPINK - 2016. SQUARE TWO
Expand All @@ -28,7 +28,7 @@ systems.

The virtual filesystem constructed from the above source directory is:

```
```tree
.
├── albums
│   ├── BLACKPINK - 2016. SQUARE ONE - Single [K-Pop] {YG Entertainment}
Expand Down 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 Conventions

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`.

### Directory Structure
## Supported Filetypes

`$music_source_dir/albums/track.ogg`
Rosé supports MP3, M4A, OGG, OPUS, and FLAC audio files and JPG and PNG image
files.

### Supported Extensions
## Tagging

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

WIP
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`.

artist1;artist2 feat. artist3
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`.

BNF TODO
An ambiguous BNF for the artist tag is:

```
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 5e94f89

Please sign in to comment.