From 6378f3f5c3f74a385560f7bd2f0021401fb09b01 Mon Sep 17 00:00:00 2001 From: John Kurkowski Date: Thu, 12 Dec 2024 19:01:00 -0800 Subject: [PATCH] Add render `--dry-run` flag * Thread new kwarg to all render helper functions * During dry run, * Do not leave changed files on disk, do not upload * Eagerly compute result stats, before temp file is deleted --- src/music/render/command.py | 17 ++++++++++++-- src/music/render/process.py | 47 +++++++++++++++++++++++++++---------- src/music/render/result.py | 8 +++++++ 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/music/render/command.py b/src/music/render/command.py index 5d64984..47dcc4f 100644 --- a/src/music/render/command.py +++ b/src/music/render/command.py @@ -51,6 +51,15 @@ " SOUNDCLOUD_ADDITIONAL_HEADERS." ), ) +@click.option( + "--dry-run", + default=False, + help=( + "Whether to actually write changes to disk or upload, while still" + " performing the render." + ), + is_flag=True, +) @click.option( "--include-main", default=None, @@ -144,6 +153,7 @@ async def main( project_dirs: list[Path], additional_headers: str, + dry_run: bool, include_main: SongVersion | None, include_instrumental: SongVersion | None, include_instrumental_dj: SongVersion | None, @@ -191,6 +201,7 @@ async def main( command = _Command( parsed_additional_headers, + dry_run, oauth_token, upload, upload_existing, @@ -208,6 +219,7 @@ class _Command: """Wrap parsed command line arguments.""" additional_headers: dict[str, str] + dry_run: bool oauth_token: str upload: bool upload_existing: bool @@ -255,7 +267,7 @@ async def _render_project( renders = [] uploads = [] - if self.upload_existing: + if self.upload_existing and not self.dry_run: uploads.append( asyncio.create_task( self.upload_process.process( @@ -270,12 +282,13 @@ async def _render_project( async for _, render in self.render_process.process( project, *self.versions, + dry_run=self.dry_run, verbose=0, vocal_loudness_worth=self.vocal_loudness_worth, ): renders.append(render) - if self.upload and render.fil.is_file(): + if self.upload and render.fil.is_file() and not self.dry_run: uploads.append( asyncio.create_task( self.upload_process.process( diff --git a/src/music/render/process.py b/src/music/render/process.py index 5df2ae8..5cc16c3 100644 --- a/src/music/render/process.py +++ b/src/music/render/process.py @@ -40,7 +40,7 @@ async def render_version( - project: ExtendedProject, version: SongVersion + project: ExtendedProject, version: SongVersion, *, dry_run: bool ) -> RenderResult: """Trigger Reaper to render the current project audio. Returns the output file. @@ -71,13 +71,23 @@ async def render_version( else: tmp_fil = out_fil.with_stem(in_name) - rm_rf(out_fil) - shutil.move(tmp_fil, out_fil) - - return RenderResult( - project, version, out_fil, datetime.timedelta(seconds=time_end - time_start) + final_fil = tmp_fil if dry_run else out_fil + result = RenderResult( + project, + version, + final_fil, + datetime.timedelta(seconds=time_end - time_start), + eager=dry_run, ) + if dry_run: + rm_rf(tmp_fil) + else: + rm_rf(final_fil) + shutil.move(tmp_fil, final_fil) + + return result + def trim_silence(fil: Path) -> None: """Trim leading and trailing silence from the given audio file, in-place. @@ -113,18 +123,19 @@ def trim_silence(fil: Path) -> None: async def _render_main( - project: ExtendedProject, *vocals: reapy.core.Track, verbose: int + project: ExtendedProject, *vocals: reapy.core.Track, dry_run: bool, verbose: int ) -> RenderResult: for vocal in vocals: vocal.unsolo() vocal.unmute() - return await render_version(project, SongVersion.MAIN) + return await render_version(project, SongVersion.MAIN, dry_run=dry_run) async def _render_version_with_muted_tracks( version: Literal[SongVersion.INSTRUMENTAL, SongVersion.INSTRUMENTAL_DJ], project: ExtendedProject, *tracks_to_mute: reapy.core.Track, + dry_run: bool, vocal_loudness_worth: float, verbose: int, ) -> RenderResult: @@ -132,12 +143,13 @@ async def _render_version_with_muted_tracks( adjust_master_limiter_threshold(project, vocal_loudness_worth), mute_tracks(tracks_to_mute), ): - return await render_version(project, version) + return await render_version(project, version, dry_run=dry_run) async def _render_a_cappella( project: ExtendedProject, *, + dry_run: bool, vocal_loudness_worth: float, verbose: int, ) -> RenderResult: @@ -147,7 +159,7 @@ async def _render_a_cappella( adjust_master_limiter_threshold(project, vocal_loudness_worth), mute_tracks(tracks_to_mute), ): - out = await render_version(project, SongVersion.ACAPPELLA) + out = await render_version(project, SongVersion.ACAPPELLA, dry_run=dry_run) trim_silence(out.fil) return out @@ -156,6 +168,7 @@ async def _render_a_cappella( async def _render_stems( project: ExtendedProject, *vocals: reapy.core.Track, + dry_run: bool, verbose: int, ) -> RenderResult: for vocal in vocals: @@ -166,7 +179,7 @@ async def _render_stems( for track in find_stems(project): track.select() with toggle_fx_for_tracks([project.master_track], is_enabled=False): - return await render_version(project, SongVersion.STEMS) + return await render_version(project, SongVersion.STEMS, dry_run=dry_run) class Process: @@ -180,6 +193,7 @@ async def process( self, project: ExtendedProject, *versions: SongVersion, + dry_run: bool, verbose: int, vocal_loudness_worth: float | None, ) -> AsyncIterator[tuple[SongVersion, RenderResult]]: @@ -202,7 +216,9 @@ async def process( results.append( ( SongVersion.MAIN, - lambda: _render_main(project, *vocals, verbose=verbose), + lambda: _render_main( + project, *vocals, dry_run=dry_run, verbose=verbose + ), self._add_task(project, SongVersion.MAIN), ) ) @@ -221,6 +237,7 @@ async def process( for track in [*vocals, *find_vox_tracks_to_mute(project)] if track ], + dry_run=dry_run, vocal_loudness_worth=vocal_loudness_worth, verbose=verbose, ), @@ -243,6 +260,7 @@ async def process( SongVersion.INSTRUMENTAL_DJ, project, *vocals, + dry_run=dry_run, vocal_loudness_worth=vocal_loudness_worth, verbose=verbose, ), @@ -256,6 +274,7 @@ async def process( SongVersion.ACAPPELLA, lambda: _render_a_cappella( project, + dry_run=dry_run, vocal_loudness_worth=vocal_loudness_worth, verbose=verbose, ), @@ -267,7 +286,9 @@ async def process( results.append( ( SongVersion.STEMS, - lambda: _render_stems(project, *vocals, verbose=verbose), + lambda: _render_stems( + project, *vocals, dry_run=dry_run, verbose=verbose + ), self._add_task(project, SongVersion.STEMS), ) ) diff --git a/src/music/render/result.py b/src/music/render/result.py index 693ece4..83619a7 100644 --- a/src/music/render/result.py +++ b/src/music/render/result.py @@ -46,12 +46,20 @@ def __init__( version: SongVersion, fil: Path, render_delta: datetime.timedelta, + *, + eager: bool = False, ): """Override. Initialize.""" super().__init__(project, version) self.fil = fil self.render_delta = datetime.timedelta(seconds=round(render_delta.seconds)) + if eager: + # Trigger computation eagerly. For example, the input file might be + # temporary and not exist later. + self.duration_delta # noqa: B018 + self.summary_stats # noqa: B018 + @cached_property def duration_delta(self) -> datetime.timedelta: """How long the audio file is.