#!/usr/bin/env python3 import re import sys from pathlib import Path from difflib import SequenceMatcher def normalize(name): name = Path(name).stem.lower() # remove brackets name = re.sub(r"\(.*?\)|\[.*?\]", "", name) # replace punctuation with spaces name = re.sub(r"[^a-z0-9]", " ", name) # collapse whitespace name = " ".join(name.split()) return name def similarity(a, b): words_a = set(normalize(a).split()) words_b = set(normalize(b).split()) if not words_a or not words_b: return 0.0 # Jaccard similarity intersection = words_a & words_b union = words_a | words_b return len(intersection) / len(union) def load_playlist(path): entries = [] with open(path, encoding="utf-8", errors="ignore") as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue entries.append(Path(line).name) return entries def compare_playlists(orig_file, new_file, threshold=0.85): orig_files = load_playlist(orig_file) new_files = load_playlist(new_file) print(f"\n=== {orig_file.parent} ===") print(f"Comparing:") print(f" {orig_file.name}") print(f" {new_file.name}") missing = [] for orig in orig_files: best_match = None best_score = 0 for candidate in new_files: score = similarity(orig, candidate) if score > best_score: best_score = score best_match = candidate if best_score >= threshold: pass # print( # f"MATCH {best_score:.1%} " # f"{orig} -> {best_match}" # ) else: missing.append((orig, best_score, best_match)) if missing: print("\nMISSING:") for orig, score, best_match in sorted( missing, key=lambda x: x[1], reverse=True, ): print( f" {orig}" f" (best={score:.1%}" f"{', candidate=' + best_match if best_match else ''})" ) return len(missing) def compare_directory(root, threshold=0.85): total_missing = 0 playlist_pairs = 0 for orig_file in root.rglob("*_orig.m3u"): new_file = orig_file.with_name( orig_file.name.replace("_orig.m3u", ".m3u") ) if not new_file.exists(): print( f"\nWARNING: Missing playlist for " f"{orig_file}" ) continue playlist_pairs += 1 total_missing += compare_playlists( orig_file, new_file, threshold, ) print("\n" + "=" * 80) print(f"Playlists compared : {playlist_pairs}") print(f"Total missing : {total_missing}") def main(): args = sys.argv[1:] threshold = 0.85 if len(args) == 3 and args[0] == "--threshold": threshold = float(args[1]) args = [args[2]] elif len(args) == 4 and args[0] == "--threshold": threshold = float(args[1]) args = [args[2], args[3]] if len(args) == 1: path = Path(args[0]) if not path.is_dir(): print("Single argument must be a directory") sys.exit(1) compare_directory(path, threshold) elif len(args) == 2: compare_playlists( Path(args[0]), Path(args[1]), threshold, ) else: print( f"Usage:\n" f" {Path(sys.argv[0]).name} " f" \n\n" f" {Path(sys.argv[0]).name} " f"\n\n" f"Optional:\n" f" --threshold 0.90" ) sys.exit(1) if __name__ == "__main__": main()