diff --git a/README.md b/README.md index 92ba633..2b42009 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,18 @@ cd Musort pip install requirements.txt ``` After that, run the program with `python3 musort.py`. + +### Docker installation +#### From source +``` Bash +git clone https://github.com/tdeerenberg/Musort.git +cd Musort +docker build -t musort . +``` + After the docker installation is complete, musort can be run with: `docker run --name musort --rm -v "/:/HostMountedFS" -it musort` + +> Tip: You could alias something like `alias musortd="docker run --name musort --rm -v "/:/HostMountedFS" -it musort"` then use `musortd` juse like `musort` usage is explained above + ## Manual (options and arguments) `musort --help` ``` USAGE: diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..9be9da9 --- /dev/null +++ b/dockerfile @@ -0,0 +1,17 @@ +FROM python:3.10-slim + +# Probably should do update/upgrade for any CVEs + +WORKDIR / + +# Set up requirements +COPY requirements.txt requirements.txt +# hadolint ignore=DL3013 +RUN python3 -m pip install pip --upgrade --no-cache-dir && \ + python3 -m pip install -r requirements.txt --no-cache-dir + +# Copy in code +COPY ./src/musort-docker.py /musort.py + +# docker run --name musort --rm -it musort --help +ENTRYPOINT ["python3", "/musort.py"] diff --git a/src/musort-docker.py b/src/musort-docker.py new file mode 100644 index 0000000..5aa6bce --- /dev/null +++ b/src/musort-docker.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python + +# Licensed under GPLv3 +# Copyright (C) 2023 tdeerenberg + +from tinytag import TinyTag +import os +import sys, getopt +import logging + +supported_formats = ["flac", "mp3", "mp2", "mp1", "opus", "ogg", "wma"] + +version = "Musort v0.2 (c) tdeerenberg" +help=\ +"""Musort (c) 2023 tdeerenberg (github.com/tdeerenberg) + +DESCRIPTION: +A Python3 program that renames all selected music/audio files in a folder with a specified naming convention + +USAGE: +musort [DIRECTORY] [NAMING_CONVENTION] [OPTIONAL_OPTIONS]... + + USAGE EXAMPLES: + musort ~/music track.title.year -s _ -r + musort /local/music disc.artist.title.album -r + musort ~/my_music track.title + +OPTIONAL OPTIONS: +-h, --help Show the help menu +-s, --separator Set the separator for the filename (ex. '-s .' -> 01.track.flac and '-s -' -> 01-track.mp3) + Default separator ( . ) will be used if none is given +-r, --recursive Rename files in subdirectories as well +-v, --version Prints the version number + +NAMING CONVENTION: +FORMAT_OPTION.FORMAT_OPTION... The amount of format options does not matter. + It can be one, two, three, even all of them. + (See FORMAT OPTIONS below for all options) + +FORMAT OPTIONS: +album album as string +albumartist album artist as string +artist artist name as string +audio_offset number of bytes before audio data begins +bitdepth bit depth for lossless audio +bitrate bitrate in kBits/s +comment file comment as string +composer composer as string +disc disc number +disc_total the total number of discs +duration duration of the song in seconds +filesize file size in bytes +genre genre as string +samplerate samples per second +title title of the song +track track number as string +track_total total number of tracks as string +year year or date as string + +SUPPORTED AUDIO FORMATS: +MP3/MP2/MP1 (ID3 v1, v1.1, v2.2, v2.3+) +Wave/RIFF +OGG +OPUS +FLAC +WMA +MP4/M4A/M4B/M4R/M4V/ALAC/AAX/AAXC""" + +class Music: + def get_files(self, directory): + """Scans the set directory for compatible audio files""" + directory = "/HostMountedFS"+os.path.abspath(directory) + self.files = list(map(lambda x: os.path.join(directory, x),os.listdir(directory))) + + def get_files_recursive(self, directory): + """Scans the set directory with subdirectory for compatible audio files""" + files = [] + + directory = "/HostMountedFS"+os.path.abspath(directory) + for a, b, c in os.walk(directory): + for d in c: + files.append(os.path.join(a,d)) + self.files = files + + def get_compatible(self): + music = [] + for file in self.files: + file_extension = file.split(".")[-1] + + if file_extension in supported_formats: + music.append(file) + + self.compatible = music + + def set_separator(self, sep): + """Sets the separator for naming the audio files + (ex. 01-songname.mp3 or 01.songname.flac)""" + if sep in ['\\', '/', '|', '*', '<', '>', '"', '?']: + sep = "_" + logging.warning("Given separator contains invalid filename symbols, defaulting to .") + self.separator = sep + + def set_format(self, val): + """Sets the naming convention of the audio files + (ex. title-artist or artist-track-title)""" + print(f"self format = {self.format}") + + # Rename files + def rename_music(self): + """Rename all compatible music files""" + + """Get the file extension (ex. .flac, .mp3, etc)""" + for file in self.compatible: + ext = file.split(".") + ext = "." + ext[-1] + + """Let TinyTag module read the audio file""" + track = TinyTag.get(file) + + """Print the progress (Current track)""" + logging.info(f"Current track: '{track.artist}' - '{track.title}'") + rename = [] + + """Uses the given format to set new filename""" + for f in self.format: + + if f == "track": + rename.append(f"{int(track.track):02}") + else: + """getattr gets attribute in track with name f""" + rename.append(getattr(track, f)) + + rename.append(self.separator) + rename.pop() + rename = ''.join(rename)+ext + """Replacing forbidden path characters in UNIX and Windows with underscores""" + for forbidden_character in ['\\', '/', '|', '*', '<', '>', '"', '?']: + if forbidden_character in rename: + logging.warning(f"Track contains forbidden path character ({forbidden_character}) in the new file name, replaced symbol with _") + rename = rename.replace(forbidden_character, "_") + + """Get the absolute path and rename the audio file""" + dst = os.path.join(os.path.abspath(os.path.dirname(file)), rename) + os.rename(file, dst) + logging.info("Actions finished") + +def main(): + level = logging.DEBUG + logging.basicConfig(level=level, format='[%(levelname)s] %(asctime)s - %(message)s') + """Runs the whole program""" + argv = sys.argv[3:] + try: + opts, args = getopt.getopt(argv, "s:r", ["sep=", "recursive="]) + except getopt.GetoptError as err: + logging.error(err) + exit() + + music = Music() + for opt, arg in opts: + if opt in ['-s', '--separator']: + logging.info(f"Using {arg} as separator") + music.set_separator(arg) + if opt in ['-r', '--recursive']: + logging.info("Running recursively") + music.get_files_recursive(sys.argv[1]) + music.get_compatible() + + if sys.argv[1] == "-h" or sys.argv[1] == '--help': + print(help) + exit() + if sys.argv[1] == '-v' or sys.argv[1] == '--version': + print(version) + exit() + try: + music.compatible + except: + logging.info("Running not recursively") + music.get_files(sys.argv[1]) + music.get_compatible() + try: + music.separator + except: + logging.info("Using default separator") + music.set_separator(".") + + music.set_format(sys.argv[2]) + music.rename_music() + +if __name__ == "__main__": + main()