from argparse import ArgumentParser from pathlib import Path from logging import basicConfig, warning, WARNING from shutil import copyfile def main(music_folder: Path): music_folder = music_folder.resolve().absolute() for m3u in music_folder.rglob("*.m3u"): copyfile(m3u, m3u.with_suffix(".m3u.bak")) with m3u.open("r") as contents: tracks = [Path(p) for p in contents.readlines()] if not all( music_folder in track.parents if track.is_absolute() else music_folder in m3u.parent.joinpath(track).resolve().parents for track in tracks ): warning(f"{m3u} contains references to tracks that are not in {music_folder} and will be skipped") continue with m3u.open("w") as updated_contents: for track in tracks: if track.is_absolute(): updated_contents.write(track.relative_to(m3u, walk_up=True).as_posix()) else: updated_contents.write(track.as_posix()) if __name__ == "__main__": basicConfig( level=WARNING, ) parser = ArgumentParser( description="Recursively traverse a music folder containing tracks and playlist files that reference them, replacing absolute paths in the playlists with equivalent relative paths." ) parser.add_argument( "music_folder", required=True, type=Path ) args = parser.parse_args() main(args.music_folder)