import argparse import logging import subprocess import pathlib from tqdm.contrib.concurrent import thread_map logging.basicConfig( filename='flac2alac.log', filemode='a', format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO ) def parse_args(): parser = argparse.ArgumentParser() parser.add_argument('-s', '--source', required=True) parser.add_argument('-d', '--destination', required=True) parser.add_argument('-j', '--jobs', type=int, default=1) return parser.parse_args() def convert(job): job['dst'].parent.mkdir(exist_ok=True) try: subprocess.run([ 'ffmpeg', '-y', # overwrite if exists '-i', str(job['src']), '-vn', # no video '-c:a', 'alac', str(job['dst']) ], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) logging.info(f'Converted {job["src"]}') except subprocess.CalledProcessError: logging.error(f'Failed to convert {job["src"]}') def main(): args = parse_args() src = pathlib.Path(args.source) dst = pathlib.Path(args.destination) jobs = list(map( lambda s: { 'src': s, 'dst': (dst / s.relative_to(src)).with_suffix('.m4a') }, sorted(src.glob('**/*.flac')) )) thread_map(convert, jobs, max_workers=args.jobs) if __name__ == '__main__': main()