70 lines
2.7 KiB
Python
70 lines
2.7 KiB
Python
import re
|
|
from collections.abc import Sequence
|
|
from pathlib import Path
|
|
|
|
from tqdm import tqdm
|
|
|
|
from karaokatalog.instructions.MoveInstruction import MoveInstruction
|
|
from karaokatalog.Song import Song
|
|
|
|
# Those characters are forbidden on Linux (few) or on Windows (many), so we strip them from filenames
|
|
# https://stackoverflow.com/a/31976060/
|
|
# Apparently, trailing dots are also prohibited, so we strip those as well:
|
|
# https://lkml.iu.edu/hypermail/linux/kernel/2203.2/01877.html
|
|
FORBIDDEN_CHARACTERS = re.compile(r'[\/<>:"\\|?*]|\.$')
|
|
|
|
|
|
def _get_canonical_song_dir(song: Song, variant: int = 0) -> Path:
|
|
"""
|
|
Get the (relative) canonical directory in which this song should be located, that is
|
|
the directory f"{song.artist}/{song.title}".
|
|
|
|
Illegal characters are replaced. If variant > 0, f" ({variant})" is appended to the result.
|
|
"""
|
|
variant_suffix = f" ({variant})" if variant > 0 else ""
|
|
return Path(FORBIDDEN_CHARACTERS.sub("", song.artist)) / Path(
|
|
FORBIDDEN_CHARACTERS.sub("", song.title) + variant_suffix
|
|
)
|
|
|
|
|
|
def move(songs: Sequence[Song], base_dir: Path) -> Sequence[MoveInstruction]:
|
|
"""
|
|
Create move instructions to move every song into the proper song directory
|
|
within the given base_dir.
|
|
"""
|
|
|
|
# We lower case all the song directories, because Windows filesystems are often
|
|
# case-insensitive, so we have to ignore casing when finding duplicates
|
|
song_dir_strs_lower = set(
|
|
str(song.dir.relative_to(base_dir)).lower() for song in songs
|
|
)
|
|
move_instructions = []
|
|
|
|
for song in tqdm(songs, unit=" songs"):
|
|
absolute_song_dir = song.dir
|
|
song_dir = absolute_song_dir.relative_to(base_dir)
|
|
song_dir_str_lower = str(song_dir).lower()
|
|
canonical_song_dir = _get_canonical_song_dir(song)
|
|
|
|
if song_dir_str_lower not in song_dir_strs_lower:
|
|
# A move instruction has already been generated for the dir this song is in
|
|
# (which is possible, because some dirs contain multiple (variants of) songs)
|
|
continue
|
|
|
|
if song_dir != canonical_song_dir:
|
|
# song_dir is not a good name, we want to replace it, so this path will soon be free
|
|
song_dir_strs_lower.remove(song_dir_str_lower)
|
|
|
|
# Find a canonical song dir variant that does not yet exist
|
|
variant = 1
|
|
while str(canonical_song_dir).lower() in song_dir_strs_lower:
|
|
canonical_song_dir = _get_canonical_song_dir(song, variant)
|
|
variant += 1
|
|
|
|
song_dir_strs_lower.add(str(canonical_song_dir).lower())
|
|
move_instructions.append(
|
|
MoveInstruction(absolute_song_dir, base_dir / canonical_song_dir)
|
|
)
|
|
|
|
return tuple(move_instructions)
|