#!/usr/bin/env python3 """prune: list or remove stems whose song is no longer in the music library. prune.py dry run: print orphans (song gone) and changed (song replaced) prune.py --apply remove the orphans prune.py --apply --changed also remove stems of replaced songs, so stemd redoes them MUSIC_DIR and STEMS_DIR as for stemd. Refuses when the music folder holds no tracks. Without --force it also refuses to remove more than 20% of the stems in one go. """ import argparse import os import sys _ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.join(_ROOT, "desktop")) sys.path.insert(0, _ROOT) from stemify.library_core import find_stale, iter_tracks, remove_stems # noqa: E402 def main(): ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("--music", default=os.environ.get("MUSIC_DIR", "/music")) ap.add_argument("--stems", default=os.environ.get("STEMS_DIR", "/stems")) ap.add_argument("--apply", action="store_true") ap.add_argument("--changed", action="store_true") ap.add_argument("--force", action="store_true") a = ap.parse_args() total = sum(1 for _ in iter_tracks(a.music, a.stems)) if not total: sys.exit(f"no tracks in {a.music}; is the share mounted?") orphans, changed = find_stale(a.music, a.stems) drop = orphans + (changed if a.changed else []) for label, items in (("orphan", orphans), ("changed", changed)): for p in items: print(f"{label}\t{os.path.relpath(p, a.stems)}") print(f"{len(orphans)} orphans, {len(changed)} changed, {total} tracks in the library") if not a.apply or not drop: return if len(drop) > 0.2 * (total + len(orphans)) and not a.force: sys.exit("that is over 20% of the stems; check the paths or pass --force") remove_stems(a.stems, drop) print(f"removed {len(drop)}") if __name__ == "__main__": main()