Git Product home page Git Product logo

rose's Introduction

Rosé

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.

Rosé manages a source directory of music releases. Given the following source directory:

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
└── NewJeans - 2022. Ditto
    ├── 01. Ditto.opus
    └── cover.jpg

Rosé produces the following virtual filesystem (duplicate information has been omitted).

virtual/
├── 1. Releases/
│   ├── BLACKPINK - 2016. SQUARE ONE - Single/
│   │   ├── 01. BLACKPINK - WHISTLE.opus
│   │   ├── 02. BLACKPINK - BOOMBAYAH.opus
│   │   └── cover.jpg
│   ├── BLACKPINK - 2016. SQUARE TWO - Single/...
│   ├── LOOΠΔ - 2017. Kim Lip - Single [NEW]/...
│   ├── LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP/...
│   └── NewJeans - 2022. Ditto - Single/...
├── 2. Releases - New/
│   └── LOOΠΔ - 2017. Kim Lip - Single [NEW]/...
├── 3. Releases - Recently Added/
│   ├── [2023-10-25] LOOΠΔ - 2017. Kim Lip - Single/...
│   ├── [2023-10-01] LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP/...
│   ├── [2023-02-28] NewJeans - 2022. Ditto - Single/...
│   ├── [2022-08-22] BLACKPINK - 2016. SQUARE TWO - Single/...
│   └── [2022-08-10] BLACKPINK - 2016. SQUARE ONE - Single/...
├── 4. Artists/
│   ├── BLACKPINK/
│   │   ├── BLACKPINK - 2016. SQUARE ONE - Single/...
│   │   └── BLACKPINK - 2016. SQUARE TWO - Single/...
│   ├── LOOΠΔ/
│   │   ├── LOOΠΔ - 2017. Kim Lip - Single [NEW]/...
│   │   └── LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP/...
│   ├── LOOΠΔ ODD EYE CIRCLE/
│   │   └── LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP/...
│   └── NewJeans/
│       └── NewJeans - 2022. Ditto - Single/...
├── 5. Genres/
│   ├── Big Room House/
│   │   └── BLACKPINK - 2016. SQUARE ONE - Single/...
│   ├── Contemporary R&B/
│   │   ├── NewJeans - 2022. Ditto - Single/...
│   │   └── LOOΠΔ - 2017. Kim Lip - Single [NEW]/...
│   ├── Future Bass/
│   │   └── LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP/...
│   ├── Dance-Pop/
│   │   ├── BLACKPINK - 2016. SQUARE ONE - Single/...
│   │   ├── BLACKPINK - 2016. SQUARE TWO - Single/...
│   │   ├── LOOΠΔ - 2017. Kim Lip - Single [NEW]/...
│   │   └── LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP/...
│   └── K-Pop/
│       ├── BLACKPINK - 2016. SQUARE ONE - Single/...
│       ├── BLACKPINK - 2016. SQUARE TWO - Single/...
│       ├── LOOΠΔ - 2017. Kim Lip - Single [NEW]/...
│       └── LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP/...
├── 6. Labels/
│   ├── ADOR/
│   │   └── NewJeans - 2022. Ditto - Single/...
│   ├── BlockBerry Creative/
│   │   ├── LOOΠΔ - 2017. Kim Lip - Single [NEW]/...
│   │   └── LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP/...
│   └── YG Entertainment/
│       ├── BLACKPINK - 2016. SQUARE ONE - Single/...
│       └── BLACKPINK - 2016. SQUARE TWO - Single/...
├── 7. Collages/
│   └── Road Trip/
│       ├── 1. BLACKPINK - 2016. SQUARE TWO - Single/...
│       └── 2. LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP/...
└── 8. Playlists/
    └── Shower/
        ├── 1. LOOΠΔ ODD EYE CIRCLE - Chaotic.opus
        ├── 2. NewJeans - Ditto.opus
        ├── 3. BLACKPINK - PLAYING WITH FIRE.opus
        └── 4. Kim Lip - Eclipse.opus

In addition to a flat directory of all releases, Rosé creates directories based on Date Added, Artist, Genre, and Label. Rosé also provides a few other concepts for organizing your music library:

  • Collages: Collections of releases.
  • Playlists: Collections of tracks.
  • New release tracking: Track new unlistened additions to the library.

Rosé's virtual filesystem organizes your music library by the metadata in the music tags. The quality of the virtual filesystem depends on the quality of the tags.

Thus, Rosé also provides a text-based interface for manually modifying metadata and a rules engine for bulk updating metadata to improve the tags of your music library.

The rules engine allows you to pattern match tracks in your music library and apply tag changes to them. For example:

$ rose rules run 'trackartist,releaseartist:^CHUU$' 'replace:Chuu'

CHUU - 2023. Howl/01. Howl.opus
      trackartist[main]: ['CHUU'] -> ['Chuu']
      releaseartist[main]: ['CHUU'] -> ['Chuu']
CHUU - 2023. Howl/02. Underwater.opus
      trackartist[main]: ['CHUU'] -> ['Chuu']
      releaseartist[main]: ['CHUU'] -> ['Chuu']
CHUU - 2023. Howl/03. My Palace.opus
      trackartist[main]: ['CHUU'] -> ['Chuu']
      releaseartist[main]: ['CHUU'] -> ['Chuu']
CHUU - 2023. Howl/04. Aliens.opus
      trackartist[main]: ['CHUU'] -> ['Chuu']
      releaseartist[main]: ['CHUU'] -> ['Chuu']
CHUU - 2023. Howl/05. Hitchhiker.opus
      trackartist[main]: ['CHUU'] -> ['Chuu']
      releaseartist[main]: ['CHUU'] -> ['Chuu']

Features

Rosé allows you to interact with and script against your music library through a virtual filesystem and through a CLI. A concise list of the features provided by the two interfaces is:

  • Filter your music by artist, genre, label, and "new"-ness
  • Create collages of releases and playlists of tracks
  • Configure directory and filename templates for releases and tracks
  • Edit release metadata as a text file
  • Run and store rules for (bulk) updating metadata
  • Extract embedded cover art to an external file
  • Flag and unflag release "new"-ness
  • Group artist aliases together under a primary artist
  • Create "phony" single releases from any individual track
  • Support for multiple artist, label, and genre tags.
  • Support for .mp3, .m4a, .ogg (vorbis), .opus, and .flac files

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

Is Rosé For You?

Rosé expects users to be comfortable with the shell. Rosé's documentation and user interface assumes that the reader is familiar with software.

Rosé does not provide a complete music system. The user is expected to compose their own system, with Rosé as one of the pieces.

Rosé is designed for large music libraries. Smaller libraries do not require the power that Rosé offers.

Rosé expects all tracks to be part of a release. Rosé also expects that each release is an immediate subdirectory of the source directory. Rosé will not work with libraries that are collections of unorganized tracks.

Rosé modifies the files that it manages, as early as the first scan (where it writes roseid tags). Rosé does not maintain a separate database; all changes are directly applied to the managed files. This is incompatible with files seeded as torrents.

Installation

Install Rosé with Nix Flakes. If you do not have Nix Flakes, you can install Nix Flakes with this installer.

Then, to install the latest release of Rosé, run:

$ nix profile install github:azuline/rose/release

Note

The master branch tracks the unstable release, whose documentation may be more up-to-date than the latest release's documentation. You can view the latest release's documentation here.

Most users should install the latest release version of Rosé. However, if you wish to install the latest unstable version of Rosé, you can do so with the command nix profile install github:azuline/rose/master.

Quickstart

Let's now get Rosé up and running!

Once Rosé is installed, let's first confirm that rose exists and is accessible:

$ rose

Usage: rose [OPTIONS] COMMAND [ARGS]...

  A music manager with a virtual filesystem.

Options:
  -v, --verbose      Emit verbose logging.
  -c, --config PATH  Override the config file location.
  --help             Show this message and exit.

Commands:
  artists      Manage artists.
  cache        Manage the read cache.
  collages     Manage collages.
  config       Utilites for configuring Rosé.
  descriptors  Manage descriptors.
  fs           Manage the virtual filesystem.
  genres       Manage genres.
  labels       Manage labels.
  playlists    Manage playlists.
  releases     Manage releases.
  rules        Run metadata update rules on the entire library.
  tracks       Manage tracks.
  version      Print version.

Note

This quickstart assumes you have a local "source directory" of music releases for Rosé to manage. Each music release must be an immediate child subdirectory of the "source directory."

Great! Next, we'll (1) configure Rosé, (2) mount the virtual filesystem, and finally (3) play music!

  1. Rosé requires a configuration file. On Linux, the configuration file is located at $XDG_CONFIG_HOME/rose/config.toml, which is typically ~/.config/rose/.config.toml. On MacOS, the configuration file is located at ~/Library/Preferences/rose/config.toml.

    Only two configuration options are required:

    # The directory of music to manage.
    # WARNING: The files in this directory WILL be modified by Rosé!
    music_source_dir = "~/.music-source"
    # The mountpoint for the virtual filesystem.
    vfs.mount_dir = "~/music"

    The full configuration specification is documented in Configuration.

  2. Now let's mount the virtual filesystem:

    $ rose fs mount
    [15:41:13] INFO: Updating cache for release BLACKPINK - 2016. SQUARE TWO
    [15:41:13] INFO: Updating cache for release BLACKPINK - 2016. SQUARE ONE
    [15:41:13] INFO: Updating cache for release LOOΠΔ - 2017. Kim Lip
    [15:41:13] INFO: Updating cache for release NewJeans - 2022. Ditto
    [15:41:13] INFO: Updating cache for release LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match
    [15:41:13] INFO: Updating cache for collage Road Trip
    [15:41:13] INFO: Updating cache for playlist Shower

    Rosé emits log lines whenever something significant is occurring. This is expected! The log lines above come from the rose fs mount command indexing the music_source_dir at startup, in order to populate the read cache.

    The virtual filesystem uses the read cache to determine the available music and its metadata. It's possible for the cache to get out of sync from the source music files. If that happens, the rose cache update is guaranteed to resynchronize them. See Maintaining the Cache for additional documentation on cache updates and synchronization.

    Now that the virtual filesystem is mounted, let's go take a look! Navigate to the configured vfs.mount_dir, and you should see your music available in the virtual filesystem!

    $ cd $vfs_mount_dir
    
    $ ls -1
    '1. Releases'
    '2. Releases - New'
    '3. Releases - Recently Added'
    '4. Artists'
    '5. Genres'
    '6. Labels'
    '7. Collages'
    '8. Playlists'
    
    $ ls -1 "1. Releases/"
    'BLACKPINK - 2016. SQUARE ONE - Single'
    'BLACKPINK - 2016. SQUARE TWO - Single'
    'LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP'
    'LOOΠΔ - 2017. Kim Lip - Single [NEW]'
    'NewJeans - 2022. Ditto - Single'
  3. Let's play some music! You should be able to open a music file in your music player of choice.

    Mine is mpv:

    $ mpv "1. Releases/LOOΠΔ ODD EYE CIRCLE - 2017. Mix & Match - EP/04. LOOΠΔ ODD EYE CIRCLE - Chaotic.opus"
     (+) Audio --aid=1 'Chaotic' (opus 2ch 48000Hz)
    File tags:
     Artist: LOOΠΔ ODD EYE CIRCLE
     Album: Mix & Match
     Album_Artist: LOOΠΔ ODD EYE CIRCLE
     Date: 2017
     Genre: K-Pop
     Title: Chaotic
     Track: 4
    AO: [pipewire] 48000Hz stereo 2ch floatp

And that's it! If desired, you can unmount the virtual filesystem with the rose fs unmount command.

Recommended Usage

Rosé alone is not a full-featured music system, and that's the point. You should compose Rosé with other great tools to create the music system that works best for you.

We recommend pairing Rosé with:

  1. A file manager, such as nnn, mc, or ranger.
  2. A media player, such as mpv.

You also need not use the complete feature set of Rosé. Everything will continue to work if you only use the virtual filesystem and ignore the metatdata tooling, and vice versa.

Rosé's CLI is also designed to make scripting against your library easy. Operations such as "edit release" and "jump to artist" can be expressed as a bash one-liner and integrated into your file manager.

Learn More

For additional documentation, please refer to the following files:

License

Copyright 2023 blissful <[email protected]>

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

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.

Contributions

Bug fixes are happily accepted!

However, please do not open a pull request for a new feature without prior discussion. Rosé is a pet project that I developed for personal use. Rosé is designed to match my specific needs and constraints, and is never destined to be widely adopted. Therefore, the feature set will remain focused and small.

Rosé is provided as-is, really!

rose's People

Contributors

azuline avatar nucle0tides avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

nucle0tides

rose's Issues

Replace "virtual dirname" concept with virtual filesystem paths

Allows us to have custom release names per view and avoids a mutable identifier flying around everywhere.

  • Update internal APIs and CLIs
  • Update documentation
  • Replace ghost_empty_writeable_directories with something more akin to the file creation special ops.
  • Delete concept in cache + elsewhere
  • Expand the VirtualPath abstraction to encapsulate generating paths & mapping from views

Remove cachetools dep

We don't care about memory, and it's in the hot path of virtual filesystem caching. We should go as close to native as possible.

Cover art extraction

warn if the image in tags differs from the image on disk; otherwise always rm embed and make external

v0.3 roadmap

this ticket covers the items from init -> 0.3, as creating separate issues is kind of silly for the initial bootstrap

browser:

  • tag reading
  • initial cache population command
  • read ops virtual fs
  • preliminary README
  • cover.{jpg,png} support
  • json dump for jq querying
  • artist/genre/label hiding (from vfs)
  • collages
  • writes
  • watchdog
  • new albums toggling
  • added_at sorted view
  • subartists / artist aliases
  • persistent track IDs
  • playlists
    • add to playlist via filesystem...
    • playlist cover art
  • support arbitrarily nested release directories (replace scandir w/ walk)
  • collage/playlist description_meta autoupdating
  • docs dir (unbundle readme; too unorganized)
  • trigger cache update after release call in virtual fs
  • add/modify/delete cover art via virtual fs. accept writes to known valid cover art filenames and ghost file rename

metadata mgmt:

  • tag writing
  • album file editing as text file
  • add validation to album file editing for release type artist roles
  • click autocomplete
  • rules engine
    • single-matcher rule execution (char normalization / artist,label,genre rename / tag splitting) / stored file
    • act on other tag (e.g. artist:beethoven role=composer)
    • multiple actions
    • dsl for shell + cli editing
    • clean up UI
    • optimize - sqlite fts5
    • cli
    • dry-run flag
    • doc
    • add (for multi-value tags, add a value, like a genre)
  • single extractor (extract a song as a single; adjust album-level tags+releasetype)

cleanup:

  • virtualfs tests
  • replace migrations system with db nuke on mig hash change
  • factor artist parsing/formatting semantics into its own package
  • no discno in single tag files
  • bug: preview-tui syscalls missing
  • add more data to stat - mtimes!
  • flatten dirs / move testdatas into $root/testdata/{cache,tagger}
  • move to per-release .rose.toml that contains uuid+new; then use discno/trackno for playlists
  • optimize FUSE (it's 100x slower rn than native, wtf??)
  • cache refresh optimization time: possibly mtime tracking w/ --force override
  • write logs to ~/.local/smthn/rose
  • emit log lines for each release deleted b/c no longer on disk (RETURNING supported by sqlite?)
  • error.py -> base_error.py
  • bug: unnecessary cache miss for Fading Up collage?
  • push sanitize into cache module and handle it all there; return (raw, sanitized) dataclasses
  • optimize cold start cache by batching disk writes after the loop
  • parallelize cache updates
  • config parsing error handling with good messages
  • bug: where artists/genre/labels deleted from tags are not evicted from cache
  • bug: multiprocessing error propagation from subproces
  • invalidate cache on config change
  • optimize watcher test
  • improve type of artist aliases; it turns out toml has dicts..
  • refactor fuse_hide_x configs to a combo of fuse_x_{white,black}list
  • sqlite locks w/ timeouts for concurrency primitive? i hate worrying about concurrent execution
  • collage/playlist update concurrency safety
  • toggle new concurrency safety
  • move supported ext and rlstypes to tagger. move supported roles to artiststr.
  • optimize playlist addition via virtualfs (i didnt realize there would be so many calls to write, we are reallocing way too many times)
  • bug: renaming rls/track while track is in a playlist results in track disappearing from playlist until forced cache update
  • feature: cover art filenames as config array
  • improvement: config value for ignore_directories
  • bug: release editor sorts tracks incorrectly: "10" sorts before "2"
  • bug: virtual filenames are not unique during playlist editing: the remapping can change IDs if two tracks have the same virtual filename. add temporary collision ids
  • bug: rename playlist art when renaming a playlist
  • feature: support parsing release virtual dirname out of path type for CLI args
  • bug: audit collage/playlist mechanics when releases are deleted/replaced during update (e.g. during dir rename)
  • improvement: before editing any metadata or something, do a mtime check, and a cache update if the mtimes dont match. so that rose doesnt cause a lost write.
  • perf: optimize the hell out whatever's making ls slow
  • refactor: switch to https://github.com/libfuse/pyfuse3; fusepy is 5 years w/out update. UPDATE: we went with llfuse instead.
  • rose cache unwatch and pid/de-duplicate detection
  • improvement: log rotation
  • bug: standard tools error in the vfs "add to playlist/collage" operation. update vfs to pretend to be normal in these situations within a time window (e.g. allow file writing when file writing actually shouldnt be allowed)
  • bug: an invalid rename operation can permalock parent directory. might be a fusepy bug; reevaluate after switch to pyfuse3
  • bug: collage vfs adder doesnt work if directories are copied from another collage or recently added, because virtual dirname has prefixes. soln: strip a few prefix regexes when attempting to look up...
  • compat: mac support (should only require supporting alts. for XDG_*)
  • do not hard delete missing music from collage/playlist; instead, add missing field
  • renaming {NEW} via virtual fs crashes in the cache update... writes the file, cache fails to refresh dirname, vfs fails
  • improvement: write rose_release_ids to audio tags too in order to make watchdog updates more robust and prevent double .rose.{uuid}.toml writes
  • process: branching strategy: have release be the stable branch; master be the unstable branch? update release to the latest release version
  • optimization: optimize cache again...
  • robustness: if release ID in tags, don't process unless force passed, emit explicit warnings
  • bug: release cache doesnt invalidate if a track is deleted from a release
  • don't schedule duplicate playlist updates (returnval propagate)

docs:

  • ingestion: tag splitting behavior
  • blacklist/whitelist use case in virtual filesystem
  • single extraction

Configurable dirname/filename templates

Depends on #7. #7 makes rest of system agnostic to virtual dirnames.

Then move artiststr into audiotags: we can use more stylistically appealing formatting in the filesystem, e.g. x, y & z multi-value delimiting.

Use Jinja templates (\s+ -> space); have a test command that prints the templates with demo values, "fs preview-templates"

  • template support
  • user-configurable templates
  • preview command
  • documentation

Callbacks for "plugins"

Support specifying scripts to be invoked at certain points, e.g. cache update events or on release edit or something else. Rose can be extended by these scripts

Allow ignoring values in rules

matcher = "trackartist,albumartist: & "
actions = ["split: & "]
ignore = ["Eli & Fur"]
$ rose rules run "trackartist,albumartist: & " "split: & " --ignore "trackartist,albumartist:Eli & Fur"

m3u8 file exporter

perhaps a part of ext? or mainline?

i want the [P] and [C] prefixes but that's because of my mobile player...

#!/usr/bin/env python

"""
This script takes every Rose .toml playlist and creates a corresponding .m3u8 playlist
file for my phone. This script does the same for collages too.
"""

import logging
import sys
import subprocess
import contextlib
import sqlite3
from pathlib import Path
from typing import Iterator

logger = logging.getLogger()
logger.setLevel(logging.INFO)
stream_formatter = logging.Formatter(
    "[%(asctime)s] %(levelname)s: %(message)s",
    datefmt="%H:%M:%S",
)
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(stream_formatter)
logger.addHandler(stream_handler)


MUSIC_SOURCE_DIR = Path.home() / '.music-source'
PLAYLISTS_DIR = MUSIC_SOURCE_DIR / '!playlists'
COLLAGES_DIR = MUSIC_SOURCE_DIR / '!collages'
MOBILE_SOURCE_PATH = "/primary/music-synced"
DATABASE_PATH = Path.home() / '.cache' / 'rose' / 'cache.sqlite3'

# Copy pasted from Rose.
@contextlib.contextmanager
def connect() -> Iterator[sqlite3.Connection]:
    conn = sqlite3.connect(
        str(DATABASE_PATH),
        detect_types=sqlite3.PARSE_DECLTYPES,
        isolation_level=None,
        timeout=15.0,
    )
    try:
        conn.row_factory = sqlite3.Row
        conn.execute("PRAGMA foreign_keys=ON")
        conn.execute("PRAGMA journal_mode=WAL")
        yield conn
    finally:
        if conn:
            conn.close()


# Update the cache before we zoom!
subprocess.run(["rose", "cache", "update"], check=True)
logger.info("Finished updating Rose's cache!")

# Convert playlists.
for p in PLAYLISTS_DIR.iterdir():
    if p.suffix == ".m3u8":
        p.unlink()
for p in PLAYLISTS_DIR.iterdir():
    if not p.suffix == ".toml":
        continue

    logger.info(f"Converting playlist {p.name}")

    with connect() as conn:
        cursor = conn.execute(
            """
            SELECT t.source_path
            FROM tracks t
            JOIN playlists_tracks pt ON pt.track_id = t.id
            WHERE pt.playlist_name = ?
            ORDER BY pt.position ASC
            """,
            (p.stem,),
        )
        source_paths = [r["source_path"] for r in cursor]
        logger.debug(f"Found source paths: {source_paths}")

    mobile_paths = [
        MOBILE_SOURCE_PATH + p.removeprefix(str(MUSIC_SOURCE_DIR)) for p in source_paths
    ]
    logger.debug(f"Converted to mobile paths: {mobile_paths}")

    m3u8_file = p.with_name('[P] ' + p.name).with_suffix('.m3u8')
    with m3u8_file.open("w") as fp:
        fp.write("#EXTM3U\n")
        for p in mobile_paths:
            fp.write(f"{p}\n")

    logger.info(f"Wrote {m3u8_file.name}!")

# Convert playlists.
for p in COLLAGES_DIR.iterdir():
    if p.suffix == ".m3u8":
        p.unlink()
for p in COLLAGES_DIR.iterdir():
    if not p.suffix == ".toml":
        continue

    logger.info(f"Converting collage {p.name}")

    with connect() as conn:
        cursor = conn.execute(
            """
            SELECT t.source_path
            FROM tracks t
            JOIN releases r ON r.id = t.release_id
            JOIN collages_releases cr ON cr.release_id = r.id
            WHERE cr.collage_name = ?
            ORDER BY cr.position ASC, t.formatted_release_position ASC
            """,
            (p.stem,),
        )
        source_paths = [r["source_path"] for r in cursor]
        logger.debug(f"Found source paths: {source_paths}")

    mobile_paths = [
        MOBILE_SOURCE_PATH + p.removeprefix(str(MUSIC_SOURCE_DIR)) for p in source_paths
    ]
    logger.debug(f"Converted to mobile paths: {mobile_paths}")

    m3u8_file = p.with_name('[C] ' + p.name).with_suffix('.m3u8')
    with m3u8_file.open("w") as fp:
        fp.write("#EXTM3U\n")
        for p in mobile_paths:
            fp.write(f"{p}\n")

    logger.info(f"Wrote {m3u8_file.name}!")

readdir is slow again

again... the jinja templates tanked the performance. caching is not much of a perf improvement due to key construction expense. i can fix it this time, but this is going to keep popping up

future thoughts:

  1. benchmark at 10k releases. i've only ran with 1k, but i don't think the virtual filesystem will scale to 10k well. luckily the rules engine should (thanks sqlite).
  2. add performance tests of some kind? this is a real regression that materially affects the user experience.
  3. rewrite virtual filesystem in zig or rust. maybe rewrite it all later.

Switch to a single, configurable "multi-value tag" delimiter

We can use the metadata rules engine instead to implement "splitting" of other tags as-needed. I'm comfortable with ;, but it's not great as some vaporwave artists might use ;.

Scared anal people can use \\ or some insane sequence, or we can potentially even support "multi-frame" to be truly correct!

Improve presentation of errors

Add a RoseExpectedError subclass that user-facing expected errors should subclass. These errors do not print their traceback (which we can implement in __main__.py. Other errors continue to print their traceback.

Validation errors should be of this kind.

Hot Cache Optimization

Not too much we can do about a cold cache, as that has a baseline that's probably around 600-800ms for 1k releases in Python.

However, a hot cache should be able to be no-opped extremely cheaply.

Notepad for future improvements:

  • Don't pull all releases and all tracks at the start. Only pull the minimal amount of data needed to check whether we need to even upsert, which are mtimes and cover image path. Batch pull the full cache data only for the releases that need an update
  • Stop doing string transformations in tight inner loops
  • Skip logics like the dirname calcs if dirty = False

Metadata Import

  • add links
  • metadata import given link from:
    • discogs
    • musicbrainz
    • tidal
    • deezer
    • itunes/apple
    • junodownload
    • beatport
    • rym
  • cover art download given link for:
    • discogs
    • musicbrainz
    • tidal
    • deezer
    • itunes/apple
    • fanart.tv
    • junodownload
    • beatport
    • rym
  • search for link in:
    • discogs
    • musicbrainz
    • tidal
    • deezer
    • itunes/apple
    • fanart.tv
    • junodownload
    • beatport

Upgrade `print` commands

  • Move current commands to print-all and add a print command that takes in UUID/path and prints one release
  • Add rose tracks print
  • Support matcher param to filter releases/tracks print-all. only allow release-level tags for release one
  • Document!

Make configuration change experience seamless

Right now, when the configuration changes, the cache is simply nuked whenever any Rose command is run, and the running virtual fs blanks. The config change should not be as disruptive to tool usage; this behavior is unintuitive and chaotic, going psychotic.

Potential solution: Run a cache update after database upgrade. And only "swap" the database once the new database is bootstrapped.

Keep current behavior if there is no database file that is getting upgraded.

Alao add a command that refreshes the config in any running Rose programs.

Custom views

Create custom views of tracks and releases based on filters.

Stored smart playlists/collages are trivial. To make the experience ergonomic, we should have a config refresh option that reads pids and sends signal to refresh config.

Tracks view

Default filenames sorted Artist, Album, Track. Only makes sense after #7.

Edit documentation

It's been slapped together rather quickly with each feature: the content is mostly there or todo, but the writing style hurts a bit.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.