from dataclasses import dataclass import click import os import subprocess INPUT_FILE_EXTS_OPUSENC = [ ".flac", ".wav", ".aiff", ] INPUT_FILE_EXTS_COPY = [ ".opus", ".ogg", # this can also contain FLAC (fry about it) ".mp3", ".m4a", ] def get_opusenc_args( input_path: str, output_path: str, bitrate: int = 192, ) -> list[str]: return [ "opusenc", "--bitrate", str(bitrate), "--music", input_path, output_path, ] @dataclass class InputFile: filepath: str relpath: str relpath_noext: str transcode: bool @click.option( "--input-dir", "-i", type=click.Path(exists=True, dir_okay=True, file_okay=False, resolve_path=True), ) @click.option( "--output-dir", "-o", type=click.Path( exists=False, dir_okay=True, file_okay=False, writable=True, resolve_path=True ), ) @click.command() def music_transcoder(input_dir, output_dir): input_files: list[InputFile] = [] for dirpath, _, filenames in os.walk(input_dir): for filename in filenames: _, ext = os.path.splitext(filename) transcode: bool if ext in INPUT_FILE_EXTS_OPUSENC: transcode = True elif ext in INPUT_FILE_EXTS_COPY: transcode = False else: print("[WARN] rejecting file:", dirpath, filename) continue filepath = os.path.join(dirpath, filename) relpath = os.path.relpath(filepath, input_dir) relpath_noext, _ = os.path.splitext(relpath) input_files.append( InputFile( filepath=filepath, relpath=relpath, relpath_noext=relpath_noext, transcode=transcode, ) ) for input_file in input_files: if input_file.transcode: output_path = os.path.join(output_dir, input_file.relpath_noext + ".opus") else: output_path = os.path.join(output_dir, input_file.relpath) if os.path.exists(output_path): print("[WARN] skipping existing file", output_path) continue output_path_dir, _ = os.path.split(output_path) os.makedirs(output_path_dir, exist_ok=True) if not input_file.transcode: print("[INFO] hardlinking lossy file →", output_path) os.link(input_file.filepath, output_path) continue print("[INFO] transcoding lossless file →", output_path) opusenc_args = get_opusenc_args(input_file.filepath, output_path) subprocess.run(opusenc_args, check=True) if __name__ == "__main__": music_transcoder()