Skip to content

Commit

Permalink
add single extractor
Browse files Browse the repository at this point in the history
  • Loading branch information
azuline committed Nov 3, 2023
1 parent 9b866a4 commit 02bb10c
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 2 deletions.
2 changes: 1 addition & 1 deletion rose/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ def _update_cache_for_releases_executor(
release_id_from_first_file = None
with contextlib.suppress(Exception):
release_id_from_first_file = AudioTags.from_file(first_audio_file).release_id
if release_id_from_first_file is not None and not force:
if release_id_from_first_file and not force:
logger.warning(
f"No-Op: Skipping release at {source_path}: files in release already have "
f"release_id {release_id_from_first_file}, but .rose.{{uuid}}.toml is missing, "
Expand Down
9 changes: 9 additions & 0 deletions rose/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
delete_release,
dump_releases,
edit_release,
extract_single_release,
remove_release_cover_art,
set_release_cover_art,
toggle_release_new,
Expand Down Expand Up @@ -235,6 +236,14 @@ def delete3(ctx: Context, release: str) -> None:
delete_release(ctx.config, release)


@releases.command()
@click.argument("track_path", type=click.Path(path_type=Path), nargs=1)
@click.pass_obj
def extract_single(ctx: Context, track_path: Path) -> None:
"""Create a single release with the given track. The given track is copied, not moved."""
extract_single_release(ctx.config, track_path)


@cli.group()
def collages() -> None:
"""Manage collages."""
Expand Down
53 changes: 52 additions & 1 deletion rose/releases.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import tomllib
from send2trash import send2trash

from rose.artiststr import ArtistMapping
from rose.artiststr import ArtistMapping, format_artist_string
from rose.audiotags import AudioTags
from rose.cache import (
STORED_DATA_FILE_REGEX,
Expand Down Expand Up @@ -320,6 +320,57 @@ def edit_release(c: Config, release_id_or_virtual_dirname: str) -> None:
update_cache_for_releases(c, [release.source_path], force=True)


def extract_single_release(c: Config, track_path: Path) -> None:
"""Takes a track and copies it into a brand new "single" release with only that track."""
# Step 1. Compute the new directory name for the single.
af = AudioTags.from_file(track_path)
dirname = f"{format_artist_string(af.artists)} - "
if af.year:
dirname += f"{af.year}. "
dirname += af.title or "Unknown Title"
# Handle directory name collisions.
collision_no = 2
original_dirname = dirname
while True:
if not (c.music_source_dir / dirname).exists():
break
dirname = f"{original_dirname} [{collision_no}]"
collision_no += 1
# Step 2. Make the new directory and copy the track. If cover art is in track's current
# directory, copy that over too.
source_path = c.music_source_dir / dirname
source_path.mkdir()
new_track_path = source_path / f"01. {af.title}{track_path.suffix}"
shutil.copyfile(track_path, new_track_path)
for f in track_path.parent.iterdir():
if f.name.lower() in c.valid_cover_arts:
shutil.copyfile(f, source_path / f.name)
break
# Step 3. Update the tags of the new track. Clear the Rose IDs too: this is a brand new track.
af = AudioTags.from_file(new_track_path)
af.album = af.title
af.release_type = "single"
af.album_artists = af.artists
af.track_number = "1"
af.disc_number = "1"
af.release_id = None
af.id = None
af.flush()
af = AudioTags.from_file(new_track_path)
# Step 4: Update the cache!
update_cache_for_releases(c, [source_path])
# Step 5: Default extracted singles to not new: if it is new, why are you meddling with it?
for f in source_path.iterdir():
if m := STORED_DATA_FILE_REGEX.match(f.name):
release_id = m[1]
break
else:
raise ReleaseDoesNotExistError(
f"Failed to parse release ID from newly created single directory {source_path}: this should be impossible"
)
toggle_release_new(c, release_id)


def resolve_release_ids(c: Config, release_id_or_virtual_dirname: str) -> tuple[str, str]:
if valid_uuid(release_id_or_virtual_dirname):
uuid = release_id_or_virtual_dirname
Expand Down
24 changes: 24 additions & 0 deletions rose/releases_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
import tomllib

from conftest import TEST_RELEASE_1
from rose.audiotags import AudioTags
from rose.cache import CachedArtist, CachedRelease, CachedTrack, connect, get_release, update_cache
from rose.config import Config
from rose.releases import (
ReleaseDoesNotExistError,
delete_release,
dump_releases,
edit_release,
extract_single_release,
remove_release_cover_art,
resolve_release_ids,
set_release_cover_art,
Expand Down Expand Up @@ -216,6 +218,28 @@ def test_edit_release(monkeypatch: Any, config: Config, source_dir: Path) -> Non
]


def test_extract_single_release(config: Config) -> None:
shutil.copytree(TEST_RELEASE_1, config.music_source_dir / TEST_RELEASE_1.name)
cover_art_path = config.music_source_dir / TEST_RELEASE_1.name / "cover.jpg"
cover_art_path.touch()
update_cache(config)
extract_single_release(config, config.music_source_dir / TEST_RELEASE_1.name / "02.m4a")
# Assert nothing happened to the files we "extracted."
assert (config.music_source_dir / TEST_RELEASE_1.name / "02.m4a").is_file()
assert cover_art_path.is_file()
# Assert that we've successfully written/copied our files.
source_path = config.music_source_dir / "BLACKPINK - 1990. Track 2"
assert source_path.is_dir()
assert (source_path / "01. Track 2.m4a").is_file()
assert (source_path / "cover.jpg").is_file()
af = AudioTags.from_file(source_path / "01. Track 2.m4a")
assert af.album == "Track 2"
assert af.track_number == "1"
assert af.disc_number == "1"
assert af.release_type == "single"
assert af.album_artists == af.artists


def test_resolve_release_ids(config: Config) -> None:
shutil.copytree(TEST_RELEASE_1, config.music_source_dir / TEST_RELEASE_1.name)
update_cache(config)
Expand Down

0 comments on commit 02bb10c

Please sign in to comment.