Files
relative-playlist-maker/relative-playlist-maker.py
T
Brennen Raimer 0885b5454e changes to accomodate potential comment lines in files
added comments to code
2026-05-04 12:44:03 -04:00

69 lines
2.8 KiB
Python

from argparse import ArgumentParser
from itertools import filterfalse
from pathlib import Path
from logging import basicConfig, warning, WARNING
from shutil import copyfile
def main(music_folder: Path):
# get the absolute path of the music folder from the argument
music_folder = music_folder.resolve().absolute()
# recursively traverse music_folder looking for .m3u files and, for each, ...
for m3u in music_folder.rglob("*.m3u"):
# create a copy in the same directory as a backup
copyfile(m3u, m3u.with_suffix(".m3u.bak"))
# read the contents of the file
with m3u.open("r") as contents:
lines = contents.readlines()
# check that all non-comment lines refer to files in the music directory
if not all(
music_folder in Path(line).parents
if Path(line).is_absolute()
# join the parent path to the relative path in line, then resolve out the relative parts
else music_folder in m3u.parent.joinpath(Path(line)).resolve().parents
# ignore lines which start with # because they are comments, not paths to tracks
for line in filterfalse(lambda l: l.strip().startswith("#"), lines)
):
# if the file contains references to files outside of the music folder, skip the m3u file and continue to the next one
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 line in lines:
# comment line. just write it.
if line.strip().startswith("#"):
updated_contents.write(line)
# line contains an absolute path
elif Path(line).is_absolute():
# get the path of the track file relative to the playlist file and write it in posix form (/ is path delimiter)
updated_contents.write(Path(line).relative_to(m3u, walk_up=True).as_posix())
# line already contains a relative path
else:
# normalize as a posix path
updated_contents.write(Path(line).as_posix())
if __name__ == "__main__":
# setup logging to terminal
basicConfig(
level=WARNING,
)
# setup a parser for the CLI
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
)
# parse the command line for the one and only argument
args = parser.parse_args()
main(args.music_folder)