Compare commits
No commits in common. "f2c0e5a4f74d9393c002afa58aeea97af04a4440" and "8676b00ae3d7cd296f95ef4e55f5cbce0c062cb9" have entirely different histories.
f2c0e5a4f7
...
8676b00ae3
25
README.md
25
README.md
@ -3,7 +3,6 @@
|
|||||||
Tools to manage an Ultrastar DX song library. Features include:
|
Tools to manage an Ultrastar DX song library. Features include:
|
||||||
|
|
||||||
1. Deduplication
|
1. Deduplication
|
||||||
2. Organization
|
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
@ -15,30 +14,10 @@ pip install -r requirements.txt
|
|||||||
|
|
||||||
## Run
|
## 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
|
|
||||||
export SONG_LIBRARY=/path/to/library
|
|
||||||
```
|
|
||||||
|
|
||||||
### Deduplication
|
### 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.
|
If your song library is stored at `/path/to/library`, run:
|
||||||
|
|
||||||
⚠️ 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
|
```bash
|
||||||
python3 -m karaokatalog.deduplicate $SONG_LIBRARY
|
python3 -m karaokatalog.deduplicate /path/to/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
|
|
||||||
```
|
```
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
from karaokatalog.deduplicate.find_duplicates import find_duplicates
|
from karaokatalog.deduplicate.find_duplicates import find_duplicates
|
||||||
from karaokatalog.deduplicate.prune import prune
|
from karaokatalog.deduplicate.prune import prune
|
||||||
from karaokatalog.get_parser import get_parser
|
|
||||||
from karaokatalog.Library import Library
|
from karaokatalog.Library import Library
|
||||||
from karaokatalog.Song import Song
|
from karaokatalog.Song import Song
|
||||||
from karaokatalog.util.get_equivalence_classes import get_equivalence_classes
|
from karaokatalog.util.get_equivalence_classes import get_equivalence_classes
|
||||||
@ -14,10 +15,23 @@ 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__":
|
if __name__ == "__main__":
|
||||||
args = get_parser(
|
args = parse_args()
|
||||||
"deduplicate", "Deduplicate UltraStar Deluxe song libraries"
|
|
||||||
).parse_args()
|
|
||||||
logging.info("Karaokatalog Deduplication started")
|
logging.info("Karaokatalog Deduplication started")
|
||||||
|
|
||||||
logging.info("Loading library")
|
logging.info("Loading library")
|
||||||
|
@ -2,7 +2,7 @@ import shutil
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from karaokatalog.instructions.Instruction import Instruction
|
from karaokatalog.deduplicate.instructions.Instruction import Instruction
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
@ -1,7 +1,7 @@
|
|||||||
import re
|
import re
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
|
||||||
from karaokatalog.instructions.DeleteInstruction import DeleteInstruction
|
from karaokatalog.deduplicate.instructions.DeleteInstruction import DeleteInstruction
|
||||||
from karaokatalog.Song import Song
|
from karaokatalog.Song import Song
|
||||||
|
|
||||||
DISCOURAGED_DIR_PATTERN = re.compile(r"/UltrastarDX/Ultrastar DX/(Unsortiert/)?")
|
DISCOURAGED_DIR_PATTERN = re.compile(r"/UltrastarDX/Ultrastar DX/(Unsortiert/)?")
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
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
|
|
@ -1,18 +0,0 @@
|
|||||||
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
|
|
@ -1,33 +0,0 @@
|
|||||||
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")
|
|
@ -1,13 +0,0 @@
|
|||||||
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
|
|
Loading…
x
Reference in New Issue
Block a user