diff --git a/serato_doctor/cli.py b/serato_doctor/cli.py index e69de29..cd48348 100644 --- a/serato_doctor/cli.py +++ b/serato_doctor/cli.py @@ -0,0 +1,49 @@ +from pathlib import Path +import argparse + +from serato_doctor.crate_parser import parse_crates +from serato_doctor.scanner import scan_audio +from serato_doctor.report import write_csv, write_missing_report + + +def main(): + parser = argparse.ArgumentParser(prog="serato-doctor") + 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")) + args = parser.parse_args() + + serato = Path(args.serato) + music = Path(args.music) + out = Path(args.out) + report = Path(args.report) + + refs = parse_crates(serato / "Subcrates") + disk = scan_audio(music) + + disk_names = {t.filename for t in disk} + + rows = [] + for ref in refs: + rows.append({ + "crate": str(ref.source), + "serato_path": str(ref.path), + "filename": ref.filename, + "exists_by_filename": ref.filename in disk_names, + }) + + missing_count = sum(1 for r in rows if not r["exists_by_filename"]) + + write_csv(rows, out) + write_missing_report(rows, report) + + print(f"Crate references: {len(refs)}") + print(f"Disk tracks: {len(disk)}") + print(f"Missing by filename: {missing_count}") + print(f"CSV: {out}") + print(f"Report: {report}") + + +if __name__ == "__main__": + main() diff --git a/serato_doctor/report.py b/serato_doctor/report.py index e69de29..171a2f1 100644 --- a/serato_doctor/report.py +++ b/serato_doctor/report.py @@ -0,0 +1,42 @@ +from collections import Counter, defaultdict +from pathlib import Path +import csv + + +def write_missing_report(rows: list[dict], out: Path) -> None: + missing = [r for r in rows if not r["exists_by_filename"]] + + crate_counts = Counter(r["crate"] for r in missing) + filename_counts = Counter(r["filename"] for r in missing) + + with out.open("w", encoding="utf-8") as f: + f.write("# Serato Doctor Missing Report\n\n") + f.write(f"Total missing references: {len(missing)}\n\n") + + f.write("## Missing by crate\n\n") + for crate, count in crate_counts.most_common(): + f.write(f"{count:5} {crate}\n") + + f.write("\n## Most common missing filenames\n\n") + for filename, count in filename_counts.most_common(100): + f.write(f"{count:5} {filename}\n") + + f.write("\n## Detail\n\n") + by_crate = defaultdict(list) + for r in missing: + by_crate[r["crate"]].append(r["filename"]) + + for crate, names in sorted(by_crate.items()): + f.write(f"\n### {crate}\n") + for name in sorted(set(names)): + f.write(f"- {name}\n") + + +def write_csv(rows: list[dict], out: Path) -> None: + with out.open("w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter( + f, + fieldnames=["crate", "serato_path", "filename", "exists_by_filename"], + ) + writer.writeheader() + writer.writerows(rows)