129 lines
5.3 KiB
Python
129 lines
5.3 KiB
Python
from pathlib import Path
|
|
import argparse
|
|
|
|
from serato_doctor.config import ScanConfig
|
|
from serato_doctor.crate_parser import load_library_crates
|
|
from serato_doctor.database_parser import parse_database
|
|
from serato_doctor.health import analyze_health
|
|
from serato_doctor.logging import configure_logging
|
|
from serato_doctor.models.library import Library
|
|
from serato_doctor.scanner import scan_filesystem
|
|
from serato_doctor.report import write_csv, write_missing_report
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(prog="serato-doctor")
|
|
parser.add_argument(
|
|
"command", nargs="?", choices=("scan", "analyze"), default="scan"
|
|
)
|
|
parser.add_argument("--serato", default=str(Path.home() / "Music/_Serato_"))
|
|
parser.add_argument(
|
|
"--music",
|
|
default=str(
|
|
Path.home() / "Library/CloudStorage/OneDrive-Personal/Jukebox"
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
"--out", default=str(Path.home() / "Desktop/serato_doctor_scan.csv")
|
|
)
|
|
parser.add_argument(
|
|
"--report",
|
|
default=str(Path.home() / "Desktop/serato_doctor_missing_report.txt"),
|
|
)
|
|
parser.add_argument(
|
|
"--reference-root",
|
|
action="append",
|
|
default=[],
|
|
help="Old library root stored in crates; may be supplied more than once",
|
|
)
|
|
parser.add_argument(
|
|
"--verbose", action="store_true", help="Write diagnostic progress to stderr"
|
|
)
|
|
parser.add_argument("--log-file", help="Write scan progress to a log file")
|
|
args = parser.parse_args()
|
|
|
|
config = ScanConfig.build(
|
|
serato=Path(args.serato),
|
|
music=Path(args.music),
|
|
out=Path(args.out),
|
|
report=Path(args.report),
|
|
reference_roots=(Path(root) for root in args.reference_root),
|
|
verbose=args.verbose,
|
|
log_file=Path(args.log_file) if args.log_file else None,
|
|
)
|
|
logger = configure_logging(config.verbose, config.log_file)
|
|
logger.info("Starting read-only library inspection")
|
|
logger.debug("Serato directory: %s", config.serato)
|
|
logger.debug("Music directory: %s", config.music)
|
|
|
|
crates = load_library_crates(config.serato, config.reference_roots)
|
|
filesystem = scan_filesystem(config.music)
|
|
database_path = config.serato / "database V2"
|
|
database = parse_database(database_path) if database_path.is_file() else None
|
|
library = Library.from_crates(
|
|
crates=crates,
|
|
tracks=filesystem.tracks,
|
|
broken_symlinks=filesystem.broken_symlinks,
|
|
database=database,
|
|
)
|
|
results = library.reconcile_by_filename()
|
|
missing_count = sum(1 for result in results if not result.exists_by_filename)
|
|
logger.info("Parsed %d crate references", len(library.references))
|
|
logger.info("Scanned %d disk tracks", len(library.tracks))
|
|
logger.info("Found %d references missing by filename", missing_count)
|
|
|
|
if args.command == "analyze":
|
|
health = analyze_health(library)
|
|
score = f"{health.score:.1f}%" if health.score is not None else "Not assessed"
|
|
logger.info("Calculated library health: %s", score)
|
|
print(f"Overall Health: {score}")
|
|
print(f"Score Basis: {health.score_basis}")
|
|
print(f"Tracks: {health.disk_tracks}")
|
|
print(f"Crate References: {health.total_references}")
|
|
print(f"References Scored: {health.scored_references}")
|
|
print(f"Healthy References: {health.healthy_references}")
|
|
print(f"Broken References: {health.missing_references}")
|
|
print(f"Unique Missing Filenames: {health.unique_missing_filenames}")
|
|
print(f"Duplicate Filename Groups: {health.duplicate_filename_groups}")
|
|
print(f"Duplicate Files: {health.duplicate_files}")
|
|
print(
|
|
"Suspected Cloud Conflict Groups: "
|
|
f"{health.suspected_cloud_conflict_groups}"
|
|
)
|
|
print(
|
|
"Suspected Cloud Conflict Files: "
|
|
f"{health.suspected_cloud_conflict_files}"
|
|
)
|
|
print(f"Unused Tracks: {health.unused_tracks}")
|
|
print(f"Suggested Matches: {health.suggested_matches}")
|
|
print(f"Broken Symlinks: {health.broken_symlinks}")
|
|
print(f"Static Crates: {health.static_crates}")
|
|
print(f"Smart Crates: {health.smart_crates}")
|
|
print(f"Smart Crate Containers: {health.smart_crate_containers}")
|
|
print(
|
|
f"Dynamic References Excluded: {health.dynamic_references_excluded}"
|
|
)
|
|
print(f"Database Entries: {health.database_entries}")
|
|
print(f"Database / Library Matches: {health.database_library_matches}")
|
|
print(f"Database Entries Outside Scan: {health.database_unmatched_entries}")
|
|
print(f"Missing Tracks in Serato: {health.database_missing_paths}")
|
|
print(
|
|
"Unique Missing Tracks in Serato: "
|
|
f"{health.database_missing_unique_filenames}"
|
|
)
|
|
print(f"Tracks Missing From Database: {health.tracks_missing_from_database}")
|
|
print(f"Duplicate Database Paths: {health.duplicate_database_paths}")
|
|
else:
|
|
write_csv(results, config.out)
|
|
write_missing_report(results, config.report)
|
|
logger.info("Wrote CSV and missing-reference reports")
|
|
print(f"Crate references: {len(library.references)}")
|
|
print(f"Disk tracks: {len(library.tracks)}")
|
|
print(f"Missing by filename: {missing_count}")
|
|
print(f"CSV: {config.out}")
|
|
print(f"Report: {config.report}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|