from __future__ import annotations import json import subprocess from pathlib import Path MAX_SECONDS = 120 MAX_BIT_ERROR = 2 MAX_ALIGN_OFFSET = 120 def chromaprint(path: Path) -> str | None: try: p = subprocess.run( ["fpcalc", "-length", str(MAX_SECONDS), "-json", str(path)], capture_output=True, text=True, timeout=180, check=True, ) value = json.loads(p.stdout).get("fingerprint") return value if isinstance(value, str) and value else None except (OSError, subprocess.SubprocessError, ValueError, json.JSONDecodeError): return None def _popcount(value: int) -> int: return value.bit_count() def similarity(a: str, b: str) -> float: """Compare Chromaprint fingerprints using the AcoustID alignment method. This is deliberately bounded to the local fingerprint comparison and does not contact the AcoustID service. """ if not a or not b: return 0.0 try: import chromaprint as cp aa = [int(x) for x in cp.decode_fingerprint(a.encode())[0]] bb = [int(x) for x in cp.decode_fingerprint(b.encode())[0]] except (ImportError, AttributeError, TypeError, ValueError): return 0.0 if not aa or not bb: return 0.0 counts = [0] * (len(aa) + len(bb) + 1) for i, av in enumerate(aa): for j in range(max(0, i - MAX_ALIGN_OFFSET), min(len(bb), i + MAX_ALIGN_OFFSET)): if _popcount(av ^ bb[j]) <= MAX_BIT_ERROR: counts[i - j + len(bb)] += 1 return max(counts) / min(len(aa), len(bb))