- Integrated `plexapi` and `python-dotenv` as dependencies in `flake.nix` and `pyproject.toml` for enhanced functionality. - Implemented new modules for audio verification and duplicate tracking, including `audio_verification.py`, `duplicate_finder.py`, and `track_verification.py`. - Updated `main.py` to utilize the new modules for identifying and managing duplicate single tracks in Lidarr, with detailed logging and confidence scoring. - Enhanced the `find_duplicate_singles` function to support audio verification results and metadata migration to Plex. - Refactored existing code for improved structure and maintainability, ensuring better integration of new features.
86 lines
3.0 KiB
Python
86 lines
3.0 KiB
Python
"""Track verification using multiple methods"""
|
|
|
|
import logging
|
|
from typing import Dict, Optional, Tuple
|
|
|
|
from audio_verification import (
|
|
check_file_properties,
|
|
check_mb_recording_id,
|
|
check_quality_profile,
|
|
compare_fingerprints,
|
|
get_audio_fingerprint,
|
|
get_file_properties,
|
|
)
|
|
from lidarr_client import get_track_info, get_trackfile_info
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def verify_audio_match(
|
|
base_url: str,
|
|
headers: Dict[str, str],
|
|
single_track_id: int,
|
|
single_track_file_id: int,
|
|
album_track_id: int,
|
|
album_track_file_id: int,
|
|
docker_mount: Optional[str] = None,
|
|
) -> Tuple[bool, Optional[str], int]:
|
|
"""Verify tracks using multiple methods. Returns (match, message, confidence_score)"""
|
|
logger.debug(
|
|
f"Verifying audio match: single trackFileId {single_track_file_id} vs album trackFileId {album_track_file_id}"
|
|
)
|
|
|
|
single_file_info = get_trackfile_info(base_url, single_track_file_id, headers)
|
|
album_file_info = get_trackfile_info(base_url, album_track_file_id, headers)
|
|
|
|
if not (single_file_info and album_file_info):
|
|
return False, "Could not fetch track file info", 0
|
|
|
|
single_path = single_file_info.get("path")
|
|
album_path = album_file_info.get("path")
|
|
if not (single_path and album_path):
|
|
return False, "Missing file paths", 0
|
|
|
|
single_track_info = get_track_info(base_url, single_track_id, headers)
|
|
album_track_info = get_track_info(base_url, album_track_id, headers)
|
|
|
|
mb_score, mb_msg = check_mb_recording_id(single_track_info, album_track_info)
|
|
quality_score, quality_msg = check_quality_profile(
|
|
single_file_info, album_file_info
|
|
)
|
|
|
|
single_props = get_file_properties(single_path, docker_mount)
|
|
album_props = get_file_properties(album_path, docker_mount)
|
|
prop_checks = check_file_properties(single_props, album_props)
|
|
|
|
single_fp = get_audio_fingerprint(single_path, docker_mount)
|
|
album_fp = get_audio_fingerprint(album_path, docker_mount)
|
|
log_context = f"single trackFileId {single_track_file_id} vs album trackFileId {album_track_file_id}"
|
|
|
|
if single_fp and album_fp:
|
|
fp_match, fp_message = compare_fingerprints(
|
|
single_fp, album_fp, log_context, return_message=True
|
|
)
|
|
fp_score = 20 if fp_match else 0
|
|
fp_msg = f"✓ Audio fingerprint match (+20)" if fp_match else f"⚠ {fp_message}"
|
|
else:
|
|
fp_score, fp_msg = 0, "⚠ Audio fingerprint unavailable"
|
|
|
|
all_checks = [
|
|
(mb_score, mb_msg),
|
|
(quality_score, quality_msg) if quality_msg else None,
|
|
*prop_checks,
|
|
(fp_score, fp_msg),
|
|
]
|
|
|
|
valid_checks = list(filter(lambda x: x is not None, all_checks))
|
|
confidence_score = sum(score for score, _ in valid_checks)
|
|
verification_results = [msg for _, msg in valid_checks]
|
|
|
|
match = confidence_score >= 70
|
|
result_message = f"Confidence: {confidence_score}/100 | " + " | ".join(
|
|
verification_results
|
|
)
|
|
|
|
return match, result_message, confidence_score
|