Compare commits

...

5 Commits

11 changed files with 112 additions and 24 deletions

View File

@ -3,6 +3,7 @@
Tools to manage an Ultrastar DX song library. Features include:
1. Deduplication
2. Organization
## Setup
@ -14,10 +15,30 @@ pip install -r requirements.txt
## Run
### Deduplication
If your song library is stored at `/path/to/library`, run:
We assume that your song library is stored at `$SONG_LIBRARY`. Replace this placeholder when running the command, or just set the variable, e.g., like this:
```bash
python3 -m karaokatalog.deduplicate /path/to/library
export SONG_LIBRARY=/path/to/library
```
### Deduplication
**Find and delete exactly duplicated songs**, i.e., songs with the same title and artist that also consist of exactly the same files in the directory.
⚠️ This will _irreversibly_ delete all song folders it considers to be exact duplicates.
Deduplication is (mostly) risk-free: As it only deletes exact duplicates, you will not lose any data (given that I've made no programming errors, which is why the operation is only _mostly_ risk-free).
```bash
python3 -m karaokatalog.deduplicate $SONG_LIBRARY
```
### Organization
**Rename/move every song folder to `$SONG_LIBRARY/<artist>/<title>`.** If such a folder already exists, a number is appended to distinguish.
Moving will not overwrite already existing files, the operation is therefore risk-free.
```bash
python3 -m karaokatalog.organize $SONG_LIBRARY
```

View File

@ -1,11 +1,10 @@
import argparse
import logging
from pathlib import Path
from tqdm import tqdm
from karaokatalog.deduplicate.find_duplicates import find_duplicates
from karaokatalog.deduplicate.prune import prune
from karaokatalog.get_parser import get_parser
from karaokatalog.Library import Library
from karaokatalog.Song import Song
from karaokatalog.util.get_equivalence_classes import get_equivalence_classes
@ -15,23 +14,10 @@ logging.basicConfig(
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="python3 -m karaokatalog.deduplicate",
description="Deduplicate UltraStar Deluxe song libraries",
)
parser.add_argument(
"library_path",
type=Path,
help="The directory which contains the songs, the one you'd also configure UltraStar Deluxe to use",
)
return parser.parse_args()
if __name__ == "__main__":
args = parse_args()
args = get_parser(
"deduplicate", "Deduplicate UltraStar Deluxe song libraries"
).parse_args()
logging.info("Karaokatalog Deduplication started")
logging.info("Loading library")

View File

@ -1,7 +1,7 @@
import re
from collections.abc import Sequence
from karaokatalog.deduplicate.instructions.DeleteInstruction import DeleteInstruction
from karaokatalog.instructions.DeleteInstruction import DeleteInstruction
from karaokatalog.Song import Song
DISCOURAGED_DIR_PATTERN = re.compile(r"/UltrastarDX/Ultrastar DX/(Unsortiert/)?")

View File

@ -0,0 +1,17 @@
import argparse
from pathlib import Path
def get_parser(module_name: str, description: str) -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog=f"python3 -m karaokatalog.{module_name}",
description=description,
)
parser.add_argument(
"library_path",
type=Path,
help="The directory which contains the songs, the one you'd also configure UltraStar Deluxe to use",
)
return parser

View File

@ -2,7 +2,7 @@ import shutil
from dataclasses import dataclass
from pathlib import Path
from karaokatalog.deduplicate.instructions.Instruction import Instruction
from karaokatalog.instructions.Instruction import Instruction
@dataclass(frozen=True)

View File

@ -0,0 +1,18 @@
from dataclasses import dataclass
from pathlib import Path
from karaokatalog.instructions.Instruction import Instruction
@dataclass(frozen=True)
class MoveInstruction(Instruction):
"""
Move the old path (might point to a file, a folder or a link) to the new path, no questions asked.
Does not override existing paths.
"""
old_path: Path
new_path: Path
def __call__(self) -> None:
raise NotImplementedError() # TODO

View File

View File

@ -0,0 +1,33 @@
import logging
from tqdm import tqdm
from karaokatalog.get_parser import get_parser
from karaokatalog.Library import Library
from karaokatalog.organize.move import move
logging.basicConfig(
format="%(asctime)s [%(levelname)s] %(message)s", level=logging.INFO
)
if __name__ == "__main__":
args = get_parser(
"organize", "Organize UltraStar Deluxe song libraries"
).parse_args()
logging.info("Karaokatalog Organization started")
logging.info("Loading library")
library = Library.from_dir(args.library_path)
logging.info("Library loaded")
logging.info("Generating move instructions")
move_instructions = move(library.songs, args.library_path)
logging.info(f"{len(move_instructions)} move instructions generated")
logging.warning(f"Moving {len(move_instructions)} songs!")
for instruction in tqdm(move_instructions, unit=" songs"):
instruction()
logging.info("Moving done")
logging.info("Karaokatalog Organization done")

View File

@ -0,0 +1,13 @@
from collections.abc import Sequence
from pathlib import Path
from karaokatalog.instructions.MoveInstruction import MoveInstruction
from karaokatalog.Song import Song
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.
"""
raise NotImplementedError() # TODO