From 59000ec4796439c2872f867320f675b4339629f8 Mon Sep 17 00:00:00 2001 From: Splendide Imaginarius <119545140+Splendide-Imaginarius@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:22:20 +0000 Subject: [PATCH] Move play-tagged options into common_loop_options Remove the play-tagged command since it's now redundant. Fixes https://github.com/arkrow/PyMusicLooper/issues/67 --- pymusiclooper/cli.py | 43 ++++++---------------------------------- pymusiclooper/handler.py | 22 +++++++++++++++++--- 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/pymusiclooper/cli.py b/pymusiclooper/cli.py index b5284b9..583d260 100644 --- a/pymusiclooper/cli.py +++ b/pymusiclooper/cli.py @@ -78,6 +78,11 @@ def common_loop_options(f): @click.option("--brute-force", is_flag=True, default=False, help=r"Check the entire audio track instead of just the detected beats. [dim yellow](Warning: may take several minutes to complete.)[/]") @click.option("--disable-pruning", is_flag=True, default=False, help="Disables filtering of the detected loop points from the initial pass.") + @click.option("--tag-names", type=str, required=False, nargs=2, help="Name of the loop metadata tags to read from, e.g. --tag-names LOOP_START LOOP_END (note: values must be integers and in sample units). Default: auto-detected.") + @click.option("--tag-offset/--no-tag-offset", is_flag=True, default=None, help="Always parse second loop metadata tag as a relative length / or as an absolute length. Default: auto-detected based on tag name.") + + @click.option("--read-tags/--no-read-tags", is_flag=True, default=None, help="Always determine loop points from metadata tags / from analysis. Default: use tags if present, analysis otherwise.") + @functools.wraps(f) def wrapper_common_options(*args, **kwargs): return f(*args, **kwargs) @@ -116,9 +121,8 @@ def play(**kwargs): handler = LoopHandler(**kwargs) in_samples = "PML_DISPLAY_SAMPLES" in os.environ - interactive_mode = "PML_INTERACTIVE_MODE" in os.environ - chosen_loop_pair = handler.choose_loop_pair(interactive_mode=interactive_mode) + chosen_loop_pair = handler.choose_loop_pair(interactive_mode=handler.interactive_mode) start_time = handler.format_time(chosen_loop_pair.loop_start, in_samples=in_samples) end_time = handler.format_time(chosen_loop_pair.loop_end, in_samples=in_samples) @@ -141,41 +145,6 @@ def play(**kwargs): print_exception(e) -@cli_main.command() -@click.option('--path', type=click.Path(exists=True), required=True, help='Path to the audio file.') -@click.option("--tag-names", type=str, required=False, nargs=2, help="Name of the loop metadata tags to read from, e.g. --tag-names LOOP_START LOOP_END (note: values must be integers and in sample units). Default: auto-detected.") -@click.option("--tag-offset/--no-tag-offset", is_flag=True, default=None, help="Always parse second loop metadata tag as a relative length / or as an absolute length. Default: auto-detected based on tag name.") -def play_tagged(path, tag_names, tag_offset): - """Skips loop analysis and reads the loop points directly from the tags present in the file.""" - try: - if tag_names is None: - tag_names = [None, None] - - looper = MusicLooper(path) - loop_start, loop_end = looper.read_tags(tag_names[0], tag_names[1], tag_offset) - - in_samples = "PML_DISPLAY_SAMPLES" in os.environ - - start_time = ( - loop_start - if in_samples - else looper.samples_to_ftime(loop_start) - ) - end_time = ( - loop_end - if in_samples - else looper.samples_to_ftime(loop_end) - ) - - rich_console.print(f"\nPlaying with looping active from [green]{end_time}[/] back to [green]{start_time}[/]") - rich_console.print("(Press [red]Ctrl+C[/] to stop looping.)") - - looper.play_looping(loop_start, loop_end) - - except Exception as e: - print_exception(e) - - @cli_main.command() @common_path_options @common_loop_options diff --git a/pymusiclooper/handler.py b/pymusiclooper/handler.py index 2d4c148..0007d22 100644 --- a/pymusiclooper/handler.py +++ b/pymusiclooper/handler.py @@ -26,8 +26,27 @@ def __init__( brute_force: bool = False, disable_pruning: bool = False, _progressbar: Progress = None, + tag_names: Optional[List[Optional[str]]] = None, + tag_offset: Optional[bool] = None, + read_tags: Optional[bool] = None, **kwargs, ): + self.filepath = path + self._musiclooper = MusicLooper(filepath=path) + + if read_tags or read_tags is None: + try: + if tag_names is None: + tag_names = [None, None] + + loop_start, loop_end = self._musiclooper.read_tags(tag_names[0], tag_names[1], tag_offset) + self.loop_pair_list = [LoopPair(_loop_start_frame_idx=None, _loop_end_frame_idx=None, note_distance=0.0, loudness_difference=0.0, loop_start=loop_start, loop_end=loop_end, score=1.0)] + self.interactive_mode = False + return + except ValueError as e: + if read_tags: + raise e + if approx_loop_position is not None: self.approx_loop_start = approx_loop_position[0] self.approx_loop_end = approx_loop_position[1] @@ -35,9 +54,6 @@ def __init__( self.approx_loop_start = None self.approx_loop_end = None - self.filepath = path - self._musiclooper = MusicLooper(filepath=path) - logging.info(f"Loaded \"{path}\". Analyzing...") self.loop_pair_list = self.musiclooper.find_loop_pairs(