107 lines
2.7 KiB
Python
107 lines
2.7 KiB
Python
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()
|