diff --git a/README.md b/README.md index db9d166..8a73335 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,89 @@ # Rosé -_In Progress_ +A virtual filesystem music library and a music metadata manager. -Rose is a Linux music library manager. +## The Virtual Filesystem Library -## Configuration +Rosé reads a source directory of albums like this: -Rose reads its configuration from `${XDG_CONFIG_HOME:-$HOME/.config}/rose/config.toml`. +``` +. +├── 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 +``` + +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: + +``` +. +├── albums +│   ├── 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] +│   ├── LOOΠΔ ODD EYE CIRCLE - 2017. Max & Match [K-Pop] +│   └── YUZION - 2019. Young Trapper [Hip Hop] +├── 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] +│   ├── 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] +│   └── YUZION +│   └── YUZION - 2019. Young Trapper [Hip Hop] +├── genres +│   ├── Hip-Hop +│   │   └── YUZION - 2019. Young Trapper [Hip Hop] +│   └── 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] +│      └── LOOΠΔ ODD EYE CIRCLE - 2017. Max & Match [K-Pop] +└── 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] + └── YG Entertainment +       ├── BLACKPINK - 2016. SQUARE ONE - Single [K-Pop] {YG Entertainment} + └── BLACKPINK - 2016. SQUARE TWO - Single [K-Pop] {YG Entertainment} +``` + +## The Metadata Manager + +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. + +Which I have yet to write. Please check back later! + +# Configuration + +Rose is configured via a TOML file located at +`${XDG_CONFIG_HOME:-$HOME/.config}/rose/config.toml`. The configuration parameters, with examples, are: @@ -19,6 +96,8 @@ fuse_mount_dir = "~/music" cache_dir = "~/.cache/rose" ``` +The `--config/-c` flag overrides the config location. + ## Library Conventions & Expectations ### Directory Structure diff --git a/rose/__main__.py b/rose/__main__.py index f80c131..607f099 100644 --- a/rose/__main__.py +++ b/rose/__main__.py @@ -1,5 +1,6 @@ import logging from dataclasses import dataclass +from pathlib import Path import click @@ -14,13 +15,16 @@ class Context: config: Config +# fmt: off @click.group() -@click.option("--verbose", "-v", is_flag=True) +@click.option("--verbose", "-v", is_flag=True, help="Emit verbose logging.") +@click.option("--config", "-c", type=click.Path(path_type=Path), help="Override the config file location.") # noqa: E501 @click.pass_context -def cli(cc: click.Context, verbose: bool) -> None: +# fmt: on +def cli(cc: click.Context, verbose: bool, config: Path | None = None) -> None: """A filesystem-driven music library manager.""" cc.obj = Context( - config=Config.read(), + config=Config.read(config_path_override=config), ) if verbose: diff --git a/rose/cache/schema.sql b/rose/cache/schema.sql index 9b1af39..6555b27 100644 --- a/rose/cache/schema.sql +++ b/rose/cache/schema.sql @@ -25,20 +25,22 @@ CREATE INDEX releases_source_path ON releases(source_path); CREATE INDEX releases_release_year ON releases(release_year); CREATE TABLE releases_genres ( - release_id TEXT, + release_id TEXT REFERENCES releases(id) ON DELETE CASCADE, genre TEXT, genre_sanitized TEXT NOT NULL, PRIMARY KEY (release_id, genre) ); +CREATE INDEX releases_genres_release_id ON releases_genres(release_id); CREATE INDEX releases_genres_genre ON releases_genres(genre); CREATE INDEX releases_genres_genre_sanitized ON releases_genres(genre_sanitized); CREATE TABLE releases_labels ( - release_id TEXT, + release_id TEXT REFERENCES releases(id) ON DELETE CASCADE, label TEXT, label_sanitized TEXT NOT NULL, PRIMARY KEY (release_id, label) ); +CREATE INDEX releases_labels_release_id ON releases_labels(release_id); CREATE INDEX releases_labels_label ON releases_labels(label); CREATE INDEX releases_labels_label_sanitized ON releases_labels(label_sanitized); @@ -47,7 +49,7 @@ CREATE TABLE tracks ( source_path TEXT NOT NULL UNIQUE, virtual_filename TEXT NOT NULL, title TEXT NOT NULL, - release_id TEXT NOT NULL REFERENCES releases(id), + release_id TEXT NOT NULL REFERENCES releases(id) ON DELETE CASCADE, track_number TEXT NOT NULL, disc_number TEXT NOT NULL, duration_seconds INTEGER NOT NULL,