Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
132 lines (114 sloc) 4.04 KB
#!/usr/bin/env python3
# file:
# vim:fileencoding=utf-8:fdm=marker:ft=python
# Copyright © 2012-2018 R.F. Smith <>.
# SPDX-License-Identifier: MIT
# Created: 2012-12-22T01:26:10+01:00
# Last modified: 2018-07-07T18:56:54+0200
Encodes WAV files from cdparanoia (“trackNN.cdda.wav”) to MP3 format.
Processing is done in parallel using as many subprocesses as the machine has
Information w.r.t. artist, song titles et cetera is gathered from a text file
called “album.json”, which should have the following info;
"title": "title of the album",
"artist": "name of the artist",
"year": 1985,
"genre": "rock",
"tracks": [
from functools import partial
import argparse
import concurrent.futures as cf
import json
import logging
import os
import subprocess
import sys
__version__ = '2.1.0'
def main(argv):
"""Entry point for make-mp3."""
parser = argparse.ArgumentParser(description=__doc__)
choices=['debug', 'info', 'warning', 'error'],
help="logging level (defaults to 'warning')"
parser.add_argument('-v', '--version', action='version', version=__version__)
args = parser.parse_args(argv)
level=getattr(logging, args.log.upper(), None), format='%(levelname)s: %(message)s'
logging.debug(f'command line arguments = {argv}')
logging.debug(f'parsed arguments = {args}')
checkfor(['lame', '--version'])
tfn = 'album.json'
with open(tfn) as jf:
data = json.load(jf)
keys = data.keys()
for key in ['title', 'year', 'genre', 'artist', 'tracks']:
if key not in keys:
logging.error(f'key "{key}" not in "{tfn}"')
sys.exit(1)'album name: ' + data['title'])'artist: ' + data['artist'])'year: ' + str(data['year']))'genre: ' + data['genre'])
num = len(data['tracks'])
with cf.ThreadPoolExecutor(max_workers=os.cpu_count()) as tp:
for idx, rv in, data=data), range(num)):
tnum = idx + 1
if rv == 0:
tt = data['tracks'][idx]'finished track {tnum}, "{tt}"')
logging.error(f'conversion of track {tnum} failed, return code {rv}')
def checkfor(args, rv=0):
Ensure that a program necessary for using this script is available.
If the required utility is not found, this function will exit the program.
args: String or list of strings of commands. A single string may not
contain spaces.
rv: Expected return value from evoking the command.
if isinstance(args, str):
if ' ' in args:
raise ValueError('no spaces in single command allowed')
args = [args]
rc =, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if rc != rv:
raise OSError'found required program "{args[0]}"')
except OSError as oops:
logging.error(f'required program "{args[0]}" not found: {oops.strerror}.')
def runmp3(idx, data):
Use the lame(1) program to convert a music file to MP3 format.
idx: track index (starts from 0)
data: album data dictionary
A tuple containing the track index and return value of lame.
num = idx + 1
args = [
'lame', '-S', '--preset', 'standard', '--tt', data['tracks'][idx], '--ta', data['artist'],
'--tl', data['title'], '--ty', str(data['year']), '--tn', '{:02d}'.format(num),
'--tg', data['genre'], f'track{num:02d}.cdda.wav', f'track{num:02d}.mp3'
rv =, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return (idx, rv)
if __name__ == '__main__':