Skip to content

Commit

Permalink
README introduction
Browse files Browse the repository at this point in the history
  • Loading branch information
azuline committed Oct 26, 2023
1 parent 5ac21ec commit f708fd4
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 139 deletions.
282 changes: 151 additions & 131 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,145 +8,155 @@ Rosé is a music manager for Unix-based systems. Rosé provides a virtual FUSE
filesystem for managing your music library and various functions for editing
and improving your music library's metadata and tags.

> [!NOTE]
> Rosé modifies the managed audio files. If you do not want to modify your
> audio files, for example because they are seeding in a bittorrent client, you
> should not use Rosé.
TODO: Video
Rosé's core functionality is taking in a directory of music and creating a
virtual filesystem based on the music's tags.

## Installation

Install Rosé with Nix Flakes. If you do not have Nix Flakes, you can install it
with [this installer](https://github.com/DeterminateSystems/nix-installer).
So for example, given the following directory of music files:

```bash
$ nix profile install github:azuline/rose#rose
```
source/
├── !collages
│   └── Road Trip.toml
├── !playlists
│   └── Shower.toml
├── BLACKPINK - 2016. SQUARE ONE
│   ├── 01. WHISTLE.opus
│   ├── 02. BOOMBAYAH.opus
│   └── cover.jpg
├── BLACKPINK - 2016. SQUARE TWO
│   ├── 01. PLAYING WITH FIRE.opus
│   ├── 02. STAY.opus
│   ├── 03. WHISTLE (acoustic ver.).opus
│   └── cover.jpg
├── LOOΠΔ - 2017. Kim Lip
│   ├── 01. Eclipse.opus
│   ├── 02. Twilight.opus
│   └── cover.jpg
├── LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match
│   ├── 01. ODD.opus
│   ├── 02. Girl Front.opus
│   ├── 03. LOONATIC.opus
│   ├── 04. Chaotic.opus
│   ├── 05. Starlight.opus
│  └── cover.jpg
└── YUZION - 2019. Young Trapper
├── 01. Look At Me!!.mp3
├── 02. In My Pocket.mp3
├── 03. Henzclub.mp3
├── 04. Ballin'.mp3
├── 05. Jealousy.mp3
├── 06. 18.mp3
├── 07. Still Love.mp3
├── 08. Next Up.mp3
└── cover.jpg
```

In the future, other packaging systems may be considered. However, I strongly
dislike Python's packaging story, hence: Nix.

## Quickstart

TODO

## Features

TODO

## Requirements

TODO

## License

Copyright 2023 blissful <blissful@sunsetglow.net>
Rosé produces the following virtual filesystem (duplicate information has been
omitted).

Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0.
```
virtual/
├── 1. Releases/
│   ├── {NEW} LOOΠΔ - 2017. Kim Lip - Single [K-Pop]/
│   │   ├── 01. LOOΠΔ - Eclipse.opus
│   │   ├── 02. LOOΠΔ - Twilight.opus
│   │   └── cover.jpg
│   ├── BLACKPINK - 2016. SQUARE ONE - Single [K-Pop] {YG Entertainment}/
│   │   └── ...
│   ├── BLACKPINK - 2016. SQUARE TWO - Single [K-Pop] {YG Entertainment}/
│   │   └── ...
│   ├── LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP [K-Pop] {BlockBerry Creative}/
│   │   └── ...
│   └── YUZION - 2019. Young Trapper [Hip Hop]/
│   └── ...
├── 2. Releases - New/
│   └── [2023-10-25] {NEW} LOOΠΔ - 2017. Kim Lip - Single [K-Pop]/...
├── 3. Releases - Recently Added/
│   ├── [2023-10-25] {NEW} LOOΠΔ - 2017. Kim Lip - Single [K-Pop]/...
│   ├── [2023-10-01] LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP [K-Pop] {BlockBerry Creative}/...
│   ├── [2022-08-22] BLACKPINK - 2016. SQUARE TWO - Single [K-Pop] {YG Entertainment}/...
│   ├── [2022-08-10] BLACKPINK - 2016. SQUARE ONE - Single [K-Pop] {YG Entertainment}/...
│   └── [2019-09-16] YUZION - 2019. Young Trapper [Hip Hop]/...
├── 4. Artists/
│   ├── BLACKPINK/
│   │   ├── BLACKPINK - 2016. SQUARE ONE - Single [K-Pop] {YG Entertainment}/...
│   │   └── BLACKPINK - 2016. SQUARE TWO - Single [K-Pop] {YG Entertainment}/...
│   ├── LOOΠΔ/
│   │   ├── {NEW} LOOΠΔ - 2017. Kim Lip - Single [K-Pop]
│   │   └── LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP [K-Pop] {BlockBerry Creative}/...
│   ├── LOOΠΔ ODD EYE CIRCLE/
│   │   └── LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP [K-Pop] {BlockBerry Creative}/...
│   └── YUZION/
│   └── YUZION - 2019. Young Trapper [Hip Hop]/...
├── 5. Genres/
│   ├── Hip Hop/
│   │   └── YUZION - 2019. Young Trapper [Hip Hop]/...
│   └── K-Pop/
│   ├── {NEW} LOOΠΔ - 2017. Kim Lip - Single [K-Pop]/...
│   ├── BLACKPINK - 2016. SQUARE ONE - Single [K-Pop] {YG Entertainment}/...
│   ├── BLACKPINK - 2016. SQUARE TWO - Single [K-Pop] {YG Entertainment}/...
│   └── LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP [K-Pop] {BlockBerry Creative}/...
├── 6. Labels/
│   ├── BlockBerry Creative/
│   │   ├── {NEW} LOOΠΔ - 2017. Kim Lip - Single [K-Pop]/...
│   │ └── LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP [K-Pop] {BlockBerry Creative}/...
│   └── YG Entertainment/
│   ├── BLACKPINK - 2016. SQUARE ONE - Single [K-Pop] {YG Entertainment}/...
│   └── BLACKPINK - 2016. SQUARE TWO - Single [K-Pop] {YG Entertainment}/...
├── 7. Collages/
│   └── Road Trip/
│   ├── 1. BLACKPINK - 2016. SQUARE TWO - Single [K-Pop] {YG Entertainment}/...
│   └── 2. LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP [K-Pop] {BlockBerry Creative}/...
└── 8. Playlists/
└── Shower/
├── 1. LOOΠΔ ODD EYE CIRCLE - Chaotic.opus
├── 2. YUZION - Jealousy.mp3
├── 3. BLACKPINK - PLAYING WITH FIRE.opus
└── 4. LOOΠΔ - Eclipse.opus
```

Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
Rosé's virtual filesystem organizes your music library by the metadata in the
music tags. In addition to a flat directory of all releases, Rosé creates
additional directories based on Date Added, Artist, Genre, and Label.

## Contributions
Rosé also provides support for creating Collages (collections of releases) and
Playlists (collections of tracks). These are configured as TOML files in the
source directory.

TODO
Because the quality of the virtual filesystem depends on the quality of the
tags, Rosé also provides functions for improving the tags of your music
library. Rosé provides an easy text-based interface for manually modifying
metadata, automatic metadata importing from third-party sources, and a rules
system to automatically apply metadata changes based on patterns.

# OLD
> [!NOTE]
> Rosé modifies the managed audio files, even on first scan. If you do not want
> to modify your audio files, for example because they are seeding in a
> bittorrent client, you should not use Rosé.
## The Virtual Filesystem
_Demo Video TBD_

Rosé reads a source directory of releases like this:
## Installation

```tree
.
├── BLACKPINK - 2016. SQUARE ONE
├── BLACKPINK - 2016. SQUARE TWO
├── LOOΠΔ - 2019. [X X]
├── LOOΠΔ - 2020. [#]
├── LOOΠΔ 1_3 - 2017. Love & Evil
├── LOOΠΔ ODD EYE CIRCLE - 2017. Max & Match
└── YUZION - 2019. Young Trapper
```
Install Rosé with Nix Flakes. If you do not have Nix Flakes, you can install it
with [this installer](https://github.com/DeterminateSystems/nix-installer).

And constructs a virtual filesystem from the source directory's audio tags. The
virtual filesystem enables viewing various subcollections of the source
directory based on multiple types of tags as a filesystem.

While music players and music servers enable viewing releases with similar
filters, those filters are only available in a proprietary UI. Rosé provides
this filtering as a filesystem, which is easily composable with other tools and
systems.

The virtual filesystem constructed from the above source directory is:

```tree
.
├── Releases
│   ├── BLACKPINK - 2016. SQUARE ONE - Single [K-Pop] {YG Entertainment}
│   ├── BLACKPINK - 2016. SQUARE TWO - Single [K-Pop] {YG Entertainment}
│   ├── LOOΠΔ 1_3 - 2017. Love & Evil [K-Pop] {BlockBerry Creative}
│   ├── LOOΠΔ - 2019. [X X] [K-Pop] {BlockBerry Creative}
│   ├── LOOΠΔ - 2020. [#] [K-Pop] {BlockBerry Creative}
│   ├── LOOΠΔ ODD EYE CIRCLE - 2017. Max & Match [K-Pop] {BlockBerry Creative}
│   └── YUZION - 2019. Young Trapper [Hip Hop] {No Label}
├── Artists
│   ├── BLACKPINK
│   │   ├── BLACKPINK - 2016. SQUARE ONE - Single [K-Pop] {YG Entertainment}
│   │ └── BLACKPINK - 2016. SQUARE TWO - Single [K-Pop] {YG Entertainment}
│   ├── LOOΠΔ
│   │   ├── LOOΠΔ - 2019. [X X] [K-Pop] {BlockBerry Creative}
│   │ └── LOOΠΔ - 2020. [#] [K-Pop] {BlockBerry Creative}
│   ├── LOOΠΔ 1_3
│   │   └── LOOΠΔ 1_3 - 2017. Love & Evil [K-Pop] {BlockBerry Creative}
│   ├── LOOΠΔ ODD EYE CIRCLE
│   │   └── LOOΠΔ ODD EYE CIRCLE - 2017. Max & Match [K-Pop] {BlockBerry Creative}
│   └── YUZION
│   └── YUZION - 2019. Young Trapper [Hip Hop] {No Label}
├── Genres
│   ├── Hip-Hop
│   │   └── YUZION - 2019. Young Trapper [Hip Hop] {No Label}
│   └── K-Pop
│      ├── BLACKPINK - 2016. SQUARE ONE - Single [K-Pop] {YG Entertainment}
│      ├── BLACKPINK - 2016. SQUARE TWO - Single [K-Pop] {YG Entertainment}
│      ├── LOOΠΔ 1_3 - 2017. Love & Evil [K-Pop] {BlockBerry Creative}
│      ├── LOOΠΔ - 2019. [X X] [K-Pop] {BlockBerry Creative}
│      ├── LOOΠΔ - 2020. [#] [K-Pop] {BlockBerry Creative}
│      └── LOOΠΔ ODD EYE CIRCLE - 2017. Max & Match [K-Pop] {BlockBerry Creative}
└── Labels
├── BlockBerry Creative
│   ├── LOOΠΔ 1_3 - 2017. Love & Evil [K-Pop] {BlockBerry Creative}
│   ├── LOOΠΔ - 2019. [X X] [K-Pop] {BlockBerry Creative}
│   ├── LOOΠΔ - 2021. [&] [K-Pop] {BlockBerry Creative}
│   └── LOOΠΔ ODD EYE CIRCLE - 2017. Max & Match [K-Pop] {BlockBerry Creative}
├── No Label
   │   └── YUZION - 2019. Young Trapper [Hip Hop] {No Label}
└── YG Entertainment
      ├── BLACKPINK - 2016. SQUARE ONE - Single [K-Pop] {YG Entertainment}
└── BLACKPINK - 2016. SQUARE TWO - Single [K-Pop] {YG Entertainment}
```bash
$ nix profile install github:azuline/rose#rose
```

## The Metadata Improvement Tooling

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 source files. If you do not want
to modify the source files, you should `chmod 444` and not use the metadata
manager!
In the future, other packaging systems may be considered. However, I strongly
dislike Python's packaging story, hence: Nix.

I have yet to write this part of the tool. Please check back later!
## Quickstart

After installing Rosé, let's first confirm that we can invoke the tool. Rosé
provides the `rose` CLI tool, which should emit help text when ran.

# Usage
```bash
$ rose

```
Usage: python -m rose [OPTIONS] COMMAND [ARGS]...
Usage: rose [OPTIONS] COMMAND [ARGS]...

A virtual filesystem for music and metadata improvement tooling.

Expand All @@ -163,27 +173,37 @@ Commands:
playlists Manage playlists.
```

## Supported Filetypes
Next...

- Mount
- Play!
- Unmount

## Features

TODO

## Requirements

Rosé supports `.mp3`, `.m4a`, `.ogg` (vorbis), `.opus`, and `.flac` audio files.

Rosé also supports JPEG and PNG cover art. The supported cover art file stems
are `cover`, `folder`, and `art`. The supported cover art file extensions are
`.jpg`, `.jpeg`, and `.png`.

## Virtual Filesystem
## License

The virtual filesystem is mounted and unmounted by `rose fs mount` and
`rose fs unmount` respectively.
Copyright 2023 blissful <blissful@sunsetglow.net>

TODO
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0.

- document supported operations
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.

## Metadata Management
## Contributions

TODO

## Systemd Unit Files

TODO; example unit files to schedule Rosé with systemd.
21 changes: 13 additions & 8 deletions rose/virtualfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,8 @@ def readdir(self, path: str, _: int) -> Iterator[str]:

if p.view == "Collages" and p.collage:
releases = list(list_collage_releases(self.config, p.collage))
pad_size = max(len(str(r[0])) for r in releases)
# Two zeros because `max(single_arg)` assumes that the single_arg is enumerable.
pad_size = max(0, 0, *[len(str(r[0])) for r in releases])
for idx, virtual_dirname, source_dir in releases:
v = f"{str(idx).zfill(pad_size)}. {virtual_dirname}"
yield v
Expand All @@ -378,7 +379,7 @@ def readdir(self, path: str, _: int) -> Iterator[str]:
if pdata is None:
raise fuse.FuseOSError(errno.ENOENT)
playlist, tracks = pdata
pad_size = max([len(str(i + 1)) for i, _ in enumerate(tracks)])
pad_size = max(0, 0, *[len(str(i + 1)) for i, _ in enumerate(tracks)])
for idx, track in enumerate(tracks):
v = f"{str(idx+1).zfill(pad_size)}. {track.virtual_filename}"
yield v
Expand Down Expand Up @@ -517,7 +518,7 @@ def release(self, path: str, fh: int) -> None:

def mkdir(self, path: str, mode: int) -> None:
logger.debug(f"Received mkdir for {path=} {mode=}")
p = parse_virtual_path(path)
p = parse_virtual_path(path, parse_release_position=False)
logger.debug(f"Parsed mkdir path as {p}")

# Possible actions:
Expand Down Expand Up @@ -703,7 +704,7 @@ class ParsedPath:
ADDED_AT_REGEX = re.compile(r"^\[[\d-]{10}\] ")


def parse_virtual_path(path: str) -> ParsedPath:
def parse_virtual_path(path: str, *, parse_release_position: bool = True) -> ParsedPath:
parts = path.split("/")[1:] # First part is always empty string.

if len(parts) == 1 and parts[0] == "":
Expand Down Expand Up @@ -811,15 +812,19 @@ def parse_virtual_path(path: str) -> ParsedPath:
return ParsedPath(
view="Collages",
collage=parts[1],
release=POSITION_REGEX.sub("", parts[2]),
release_position=m[1] if (m := POSITION_REGEX.match(parts[2])) else None,
release=POSITION_REGEX.sub("", parts[2]) if parse_release_position else parts[2],
release_position=m[1]
if parse_release_position and (m := POSITION_REGEX.match(parts[2]))
else None,
)
if len(parts) == 4:
return ParsedPath(
view="Collages",
collage=parts[1],
release=POSITION_REGEX.sub("", parts[2]),
release_position=m[1] if (m := POSITION_REGEX.match(parts[2])) else None,
release=POSITION_REGEX.sub("", parts[2]) if parse_release_position else parts[2],
release_position=m[1]
if parse_release_position and (m := POSITION_REGEX.match(parts[2]))
else None,
file=POSITION_REGEX.sub("", parts[3]),
file_position=m[1] if (m := POSITION_REGEX.match(parts[3])) else None,
)
Expand Down

0 comments on commit f708fd4

Please sign in to comment.