From a77d14166a5e2d67346987624524da35b01591a9 Mon Sep 17 00:00:00 2001 From: blissful Date: Wed, 1 Nov 2023 23:26:00 -0400 Subject: [PATCH] add cli for ad hoc rule execution --- README.md | 19 +++++++++--------- rose/__init__.py | 14 ++++++------- rose/cli.py | 21 ++++++++++++++++--- rose/rules.py | 52 ++++++++++++++++++------------------------------ 4 files changed, 54 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index a6ea4c3..fc2adf3 100644 --- a/README.md +++ b/README.md @@ -192,9 +192,9 @@ $ nix profile install github:azuline/rose/release ``` > [!NOTE] -> The default branch tracks the unstable release, whose documentation may be +> 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 at https://github.com/azuline/rose/blob/release/README.md. +> latest release's documentation [here](https://github.com/azuline/rose/blob/release/README.md). 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 @@ -212,7 +212,7 @@ $ rose Usage: rose [OPTIONS] COMMAND [ARGS]... - A virtual filesystem for music and metadata improvement tooling. + A music manager with a virtual filesystem. Options: -v, --verbose Emit verbose logging. @@ -220,11 +220,13 @@ Options: --help Show this message and exit. Commands: - cache Manage the read cache. - collages Manage collages. - fs Manage the virtual library. - releases Manage releases. - playlists Manage playlists. + cache Manage the read cache. + collages Manage collages. + completion Print a shell completion script. + fs Manage the virtual library. + metadata Run metadata improvement tools + playlists Manage playlists. + releases Manage releases. ``` > [!NOTE] @@ -319,7 +321,6 @@ finally (3) play music! Artist: LOOΠΔ ODD EYE CIRCLE Album: Mix & Match Album_Artist: LOOΠΔ ODD EYE CIRCLE - Comment: Cat #: WMED0709 Date: 2017 Genre: K-Pop Title: Chaotic diff --git a/rose/__init__.py b/rose/__init__.py index a9ec6e6..611cca0 100644 --- a/rose/__init__.py +++ b/rose/__init__.py @@ -25,17 +25,17 @@ # captures logging output on its own, so by default, we do not attach our own. if "pytest" not in sys.modules or LOG_EVEN_THOUGH_WERE_IN_TEST: # pragma: no cover stream_template = "[%(asctime)s] %(levelname)s: %(message)s" - if LOG_EVEN_THOUGH_WERE_IN_TEST: - stream_template = "[ts=%(asctime)s] [pid=%(process)d] [src=%(name)s:%(lineno)s] %(levelname)s: %(message)s" # noqa: E501 - stream_formatter = logging.Formatter(stream_template, datefmt="%H:%M:%S") + verbose_template = "[ts=%(asctime)s.%(msecs)d] [pid=%(process)d] [src=%(name)s:%(lineno)s] %(levelname)s: %(message)s" # noqa: E501 + + stream_formatter = logging.Formatter( + stream_template if not LOG_EVEN_THOUGH_WERE_IN_TEST else verbose_template, + datefmt="%H:%M:%S", + ) stream_handler = logging.StreamHandler(sys.stderr) stream_handler.setFormatter(stream_formatter) logger.addHandler(stream_handler) - file_formatter = logging.Formatter( - "[ts=%(asctime)s.%(msecs)d] [pid=%(process)d] [src=%(name)s:%(lineno)s] %(levelname)s: %(message)s", # noqa: E501 - datefmt="%H:%M:%S", - ) + file_formatter = logging.Formatter(verbose_template, datefmt="%H:%M:%S") file_handler = logging.handlers.RotatingFileHandler( LOGFILE, maxBytes=20 * 1024 * 1024, diff --git a/rose/cli.py b/rose/cli.py index 950343a..9c41046 100644 --- a/rose/cli.py +++ b/rose/cli.py @@ -48,7 +48,8 @@ set_release_cover_art, toggle_release_new, ) -from rose.rules import execute_stored_metadata_rules +from rose.rule_parser import MetadataRule +from rose.rules import execute_metadata_rule, execute_stored_metadata_rules from rose.virtualfs import VirtualPath, mount_virtualfs, unmount_virtualfs from rose.watcher import start_watchdog @@ -71,7 +72,7 @@ class Context: @click.pass_context # fmt: on def cli(cc: click.Context, verbose: bool, config: Path | None = None) -> None: - """A virtual filesystem for music and metadata improvement tooling.""" + """A music manager with a virtual filesystem.""" cc.obj = Context( config=Config.parse(config_path_override=config), ) @@ -398,11 +399,25 @@ def metadata() -> None: """Run metadata improvement tools""" +@metadata.command() +@click.argument("matcher", type=str, nargs=1) +@click.argument("actions", type=str, nargs=-1) +@click.option("--yes", "-y", is_flag=True, help="Bypass confirmation prompts.") +@click.pass_obj +def run_rule(ctx: Context, matcher: str, actions: list[str], yes: bool) -> None: + """Run an ad hoc metadata rule""" + if not actions: + logger.info("No-Op: No actions passed") + return + rule = MetadataRule.parse(matcher, actions) + execute_metadata_rule(ctx.config, rule, confirm_yes=not yes) + + @metadata.command() @click.option("--yes", "-y", is_flag=True, help="Bypass confirmation prompts.") @click.pass_obj def run_stored_rules(ctx: Context, yes: bool) -> None: - """Run the metadata rules stored in the config""" + """Run the stored metadata rules defined in the config""" execute_stored_metadata_rules(ctx.config, confirm_yes=not yes) diff --git a/rose/rules.py b/rose/rules.py index 5e3e187..289a01a 100644 --- a/rose/rules.py +++ b/rose/rules.py @@ -147,6 +147,7 @@ def execute_metadata_rule( if fields_to_update == "matched": fields_to_update = rule.matcher.tags for field in fields_to_update: + # fmt: off if field == "tracktitle": tags.title = execute_single_action(act, tags.title) potential_changes.append(("title", origtags.title, tags.title)) @@ -161,22 +162,16 @@ def execute_metadata_rule( potential_changes.append(("year", origtags.year, tags.year)) elif field == "tracknumber": tags.track_number = execute_single_action(act, tags.track_number) - potential_changes.append( - ("track_number", origtags.track_number, tags.track_number) - ) # noqa: E501 + potential_changes.append(("track_number", origtags.track_number, tags.track_number)) # noqa: E501 elif field == "discnumber": tags.disc_number = execute_single_action(act, tags.disc_number) - potential_changes.append( - ("disc_number", origtags.disc_number, tags.disc_number) - ) # noqa: E501 + potential_changes.append(("disc_number", origtags.disc_number, tags.disc_number)) # noqa: E501 elif field == "albumtitle": tags.album = execute_single_action(act, tags.album) potential_changes.append(("album", origtags.album, tags.album)) elif field == "releasetype": tags.release_type = execute_single_action(act, tags.release_type) or "unknown" - potential_changes.append( - ("release_type", origtags.release_type, tags.release_type) - ) # noqa: E501 + potential_changes.append(("release_type", origtags.release_type, tags.release_type)) # noqa: E501 elif field == "genre": tags.genre = execute_multi_value_action(act, tags.genre) potential_changes.append(("genre", origtags.genre, tags.genre)) @@ -185,45 +180,36 @@ def execute_metadata_rule( potential_changes.append(("label", origtags.label, tags.label)) elif field == "trackartist": tags.artists.main = execute_multi_value_action(act, tags.artists.main) - potential_changes.append(("main", origtags.artists.main, tags.artists.main)) + potential_changes.append(("trackartist[main]", origtags.artists.main, tags.artists.main)) # noqa: E501 tags.artists.guest = execute_multi_value_action(act, tags.artists.guest) - potential_changes.append(("guest", origtags.artists.guest, tags.artists.guest)) + potential_changes.append(("trackartist[guest]", origtags.artists.guest, tags.artists.guest)) # noqa: E501 tags.artists.remixer = execute_multi_value_action(act, tags.artists.remixer) - potential_changes.append( - ("remixer", origtags.artists.remixer, tags.artists.remixer) - ) # noqa: E501 + potential_changes.append(("trackartist[remixer]", origtags.artists.remixer, tags.artists.remixer)) # noqa: E501 tags.artists.producer = execute_multi_value_action(act, tags.artists.producer) - potential_changes.append( - ("producer", origtags.artists.producer, tags.artists.producer) - ) # noqa: E501 + potential_changes.append(("trackartist[producer]", origtags.artists.producer, tags.artists.producer)) # noqa: E501 tags.artists.composer = execute_multi_value_action(act, tags.artists.composer) - potential_changes.append( - ("composer", origtags.artists.composer, tags.artists.composer) - ) # noqa: E501 + potential_changes.append(("trackartist[composer]", origtags.artists.composer, tags.artists.composer)) # noqa: E501 tags.artists.djmixer = execute_multi_value_action(act, tags.artists.djmixer) - potential_changes.append( - ("djmixer", origtags.artists.djmixer, tags.artists.djmixer) - ) # noqa: E501 + potential_changes.append(("trackartist[djmixer]", origtags.artists.djmixer, tags.artists.djmixer)) # noqa: E501 elif field == "albumartist": - # fmt: off tags.album_artists.main = execute_multi_value_action(act, tags.album_artists.main) # noqa: E501 - potential_changes.append(("main", origtags.album_artists.main, tags.album_artists.main)) # noqa: E501 + potential_changes.append(("albumartist[main]", origtags.album_artists.main, tags.album_artists.main)) # noqa: E501 tags.album_artists.guest = execute_multi_value_action(act, tags.album_artists.guest) # noqa: E501 - potential_changes.append(("guest", origtags.album_artists.guest, tags.album_artists.guest)) # noqa: E501 + potential_changes.append(("albumartist[guest]", origtags.album_artists.guest, tags.album_artists.guest)) # noqa: E501 tags.album_artists.remixer = execute_multi_value_action(act, tags.album_artists.remixer) # noqa: E501 - potential_changes.append(("remixer", origtags.album_artists.remixer, tags.album_artists.remixer)) # noqa: E501 + potential_changes.append(("albumartist[remixer]", origtags.album_artists.remixer, tags.album_artists.remixer)) # noqa: E501 tags.album_artists.producer = execute_multi_value_action(act, tags.album_artists.producer) # noqa: E501 - potential_changes.append(("producer", origtags.album_artists.producer, tags.album_artists.producer)) # noqa: E501 + potential_changes.append(("albumartist[producer]", origtags.album_artists.producer, tags.album_artists.producer)) # noqa: E501 tags.album_artists.composer = execute_multi_value_action(act, tags.album_artists.composer) # noqa: E501 - potential_changes.append(("composer", origtags.album_artists.composer, tags.album_artists.composer)) # noqa: E501 + potential_changes.append(("albumartist[composer]", origtags.album_artists.composer, tags.album_artists.composer)) # noqa: E501 tags.album_artists.djmixer = execute_multi_value_action(act, tags.album_artists.djmixer) # noqa: E501 - potential_changes.append(("djmixer", origtags.album_artists.djmixer, tags.album_artists.djmixer)) # noqa: E501 - # fmt: on + potential_changes.append(("albumartist[djmixer]", origtags.album_artists.djmixer, tags.album_artists.djmixer)) # noqa: E501 + # fmt: on # Compute real changes by diffing the tags, and then store. changes = [(x, y, z) for x, y, z in potential_changes if y != z] if changes: - actionable_audiotags.append((tags, potential_changes)) + actionable_audiotags.append((tags, changes)) else: logger.debug(f"Skipping matched track {tags.path}: no changes calculated off tags") if not actionable_audiotags: @@ -288,8 +274,8 @@ def execute_metadata_rule( f"{tags.path} changes: {' //// '.join([str(x)+' -> '+str(y) for _, x, y in changes])}" ) tags.flush() - click.echo() + click.echo() click.echo(f"Applied tag changes to {len(actionable_audiotags)} tracks!")