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