diff --git a/media_editing/reference_playlists_to_library.py b/media_editing/reference_playlists_to_library.py index 9382d77..9e62467 100755 --- a/media_editing/reference_playlists_to_library.py +++ b/media_editing/reference_playlists_to_library.py @@ -5,7 +5,8 @@ import re import logging import argparse from pathlib import Path -from difflib import SequenceMatcher + +from serverlibrary import words, word_similarity, score_match_jaccard, score_match_fuzzy, combined_score WORKDIR = Path("/data/navidrome/music/") PLAYLIST_DIR = WORKDIR / "playlists" @@ -28,44 +29,6 @@ class CustomLogger(logging.Logger): logging.setLoggerClass(CustomLogger) logger: CustomLogger = logging.getLogger(__name__) # type: ignore[assignment] - -def words(text): - return set(filter(None, re.split(r"[^a-z0-9]+", text.lower()))) - - -def word_similarity(a: str, b: str) -> float: - return SequenceMatcher(None, a, b).ratio() - - -def score_match_jaccard(search_words, candidate_words): - intersection = search_words & candidate_words - union = search_words | candidate_words - - if not union: - return 0.0 - - return len(intersection) / len(union) - - -def score_match_fuzzy(search_words, candidate_words): - total = 0.0 - - for sw in search_words: - best = 0.0 - - for cw in candidate_words: - best = max(best, word_similarity(sw, cw)) - - total += best - - # logger.trace(f"Total: {total}/{len(search_words)}") - return total / len(search_words) - - -def combined_score(score_fuzzy: float, score_jaccard: float) -> float: - return score_fuzzy * weight_fuzzy + score_jaccard * weight_jaccard - - def find_folder(directory: Path, folder: str) -> list[Path]: matches: list[tuple[Path, float]] = [] search_words = words(folder) @@ -78,7 +41,7 @@ def find_folder(directory: Path, folder: str) -> list[Path]: score_fuzzy = score_match_fuzzy(search_words, candidate_words) score_jaccard = score_match_jaccard(search_words, candidate_words) - score = combined_score(score_fuzzy, score_jaccard) + score = combined_score(score_fuzzy, score_jaccard, weight_fuzzy, weight_jaccard) logger.trace(f"Candidate: {score:.2f}:{candidate_words}") if score > folder_score_threshold: matches.append((candidate, score)) @@ -216,7 +179,7 @@ def find_replacement(old_path: Path): score_fuzzy = score_match_fuzzy(search_words, candidate_words) score_jaccard = score_match_jaccard(search_words, candidate_words) - score = combined_score(score_fuzzy, score_jaccard) + score = combined_score(score_fuzzy, score_jaccard, weight_fuzzy, weight_jaccard) logger.trace( f"{filename_structure} Words: {candidate_words} {score:.2f} / {best_score:.2f}, " f"{weight_fuzzy:.2} * {score_fuzzy:.2f} + {weight_jaccard:.2} * {score_jaccard:.2f} " diff --git a/serverlibrary/__init__.py b/serverlibrary/__init__.py index 99dc9a5..25d2a92 100755 --- a/serverlibrary/__init__.py +++ b/serverlibrary/__init__.py @@ -2,3 +2,8 @@ from .core import safe_rename from .core import safe_delete from .core import get_audio_tracks from .core import get_subtitle_tracks +from .core import words +from .core import word_similarity +from .core import score_match_jaccard +from .core import score_match_fuzzy +from .core import combined_score diff --git a/serverlibrary/core.py b/serverlibrary/core.py index adb41dc..d16e599 100755 --- a/serverlibrary/core.py +++ b/serverlibrary/core.py @@ -5,6 +5,7 @@ import subprocess import json import re from typing import Any +from difflib import SequenceMatcher # ----------------------------- @@ -16,7 +17,8 @@ def safe_delete(path: Path): else: path.unlink() -def safe_rename(src: Path, dst: Path, dry: bool=False, confirm: bool=False, silent=False): +def safe_rename(src: Path, dst: Path, dry: bool=False, confirm: bool=False, + silent=False,overwrite=False) -> (str, bool): """ Safely rename a file or directory without overwriting existing files. Automatically appends _1, _2, ... if needed. @@ -45,14 +47,18 @@ def safe_rename(src: Path, dst: Path, dry: bool=False, confirm: bool=False, sile print(ret) if dry: - return + return ret, True if confirm: input("Press Enter to confirm...") dst.parent.mkdir(parents=True, exist_ok=True) + + if dst.exists() and not overwrite: + print("File already exists...") + return "",False src.rename(dst) - return ret + return ret, True def get_audio_tracks( source: str, title: int | None = None, handbrake: bool = False) -> list[dict]: """ @@ -197,4 +203,42 @@ def get_subtitle_tracks(source): "primary": False, }) - return tracks \ No newline at end of file + return tracks + + +def words(text): + return set(filter(None, re.split(r"[^a-z0-9]+", text.lower()))) + + +def word_similarity(a: str, b: str) -> float: + return SequenceMatcher(None, a, b).ratio() + + +def score_match_jaccard(search_words, candidate_words): + intersection = search_words & candidate_words + union = search_words | candidate_words + + if not union: + return 0.0 + + return len(intersection) / len(union) + + +def score_match_fuzzy(search_words, candidate_words): + total = 0.0 + + for sw in search_words: + best = 0.0 + + for cw in candidate_words: + best = max(best, word_similarity(sw, cw)) + + total += best + + # logger.trace(f"Total: {total}/{len(search_words)}") + return total / len(search_words) + + +def combined_score(score_fuzzy: float, score_jaccard: float, + weight_fuzzy: float, weight_jaccard: float) -> float: + return score_fuzzy * weight_fuzzy + score_jaccard * weight_jaccard \ No newline at end of file