FLAC compression levels are (only) a trade of between encoding time and file size. The decoding time is pretty much independent of compression rate. In the following I will refer to the compression levels 0, ..., 8 as FLAC-0, ..., FLAC-8.
In short: I recommend FLAC-4!
The Easy Solutions
Obviously:
If I don't care about encoding time and since space is money, I take the highest compression level FLAC-8.
If I don't care about space but want to get behind this as fast as possible, I take the lowest compression level FLAC-0.
The Difficult Solution
Where is the right middle between file size and encoding time? I stumbled upon Nathan Zachary's article on this question but he compares just two files, encodes them just once (encoding time hugely varies according to the sideload of the computer) and tables are hard to read compared to graphs.
So, inspired by this, I redid his measurements with five complete albums each in a different genre and encoded each file/track 10 times.
Procedure:
- Rip album with
abcde
and proper cdparanoia
settings to uncompressed WAV.
- Convert every file 10 times for each compression level (FLAC-0 to FLAC-8) and take the mean encoding time relative to FLAC-0 and the file size relative to FLAC-0.
- For this I disabled internet connection, all periodic jobs (
cronjobs
) and nearly everything else so that really mostly the compression runs and as less as possible interfers.
This measure should be pretty much independent of the used hardware. I used flac version 1.3.2 on Arch Linux using flac <infile> --compression-level-X -f -o flacX.flac
.
Efficiency
If you multiply the relative size with the relative encoding/compression time, you get a value for the badness. But since this badness is mostly governed by the relative time, the graphs would vastly overlap. So, to clear the graph up, I just mirrored the badness into a goodness, I call efficiency here.
Findings
From FLAC-4 on, the compression time explodes BUT there are two surprises:
There is a significant reduction in file size between FLAC-3 and FLAC-4 depending on the music genre: Classical music has a way lower compression using FLAC-4. I assume that this is because FLAC uses a linear prediction model for compression that does less well with more complex (less linear) music.
For non-classical music, FLAC-3 is even significantly worse than FLAC-2 in term of file size.

Recommendations
I recommend using compression level FLAC-4.
Going higher significantly increases encoding time with marginal improvement in file size reduction (mean reduction from FLAC-4 to FLAC-8 in this test is 1.2 % with a 182 % increase in mean compression time).
Appendix
Albums
I just took the first five random CDs (listed below) that I thought of represent different field of music. The links are intentionally going to Amazon to provide an easy possibility to take a glimpse into the music/to get an idea of the music since it does make a significant difference in the compression.
Program
For this task I wrote a python
program that goes through all sub-folders (the albums) in a given folder (--directory <folder>
) to test all .wav files and group/plot them by their sub-folder name.
<folder>
Album 1
Album 2
...
The analysis will be saved in a file --outfile <file1>
.
To plot, use --infile <file1>
and --outfile <file2>
.
#!/usr/bin/python3
#encoding=utf8
import os, sys, subprocess, argparse
from datetime import datetime, timedelta
from os.path import isfile, isdir, join
import numpy as np
import matplotlib.pyplot as plt
import pickle as pkl
parser = argparse.ArgumentParser(description='Analyse flac compression and conversion time')
group = parser.add_mutually_exclusive_group()
group.add_argument('-d', '--directory', help="Input folder", type=str)
group.add_argument('-if', '--infile', help="Plot saved stats (pickle file)", type=str)
parser.add_argument('-of', '--outfile', help="Output file", type=str, required=True)
parser.add_argument('-c', '--cycles', help="Number of cycles for each file", type=int, default=5)
parser.add_argument('-C', '--maxcompression', help="Max compression level", type=int, default=8)
args = parser.parse_args()
args.maxcompression += 1
############################################################
xlabel = 'FLAC Compression Factor'
ylabel_size = 'Size Relative to FLAC-0'
ylabel_time = 'Mean Compression Time\nOver {} Cycles Relative to FLAC-0 [s]'.format(args.cycles)
ylabel_efficiency = r'Efficiency: $(-1)\cdot$ Fraction Time $\cdot$ Fraction Size $+ 2$'
############################################################
# Analyse and write mode
if not args.infile:
if isdir(args.directory):
mypath = args.directory
else:
raise ValueError('Folder {} does not exist!'.format(args.directory))
folders = [f for f in os.listdir(mypath) if isdir(join(mypath, f))]
print('Found folders: {}'.format(folders))
# Create temporary working folder
temp_folder = 'temp_{}'.format(os.getpid())
if not os.path.exists(temp_folder):
os.makedirs(temp_folder)
# Every analysis will be storen in stats
stats = {}
remove = []
for folder in folders:
stats[folder] = {}
stats[folder]['files'] = [f for f in os.listdir(mypath+folder) if isfile(join(mypath+folder, f)) and f.endswith('.wav')]
if len(stats[folder]['files']) == 0:
print('No .wav files found in {}. Skipping.'.format(folder))
remove.append(folder)
stats.pop(folder, None)
else:
stats[folder]['stats'] = np.empty([len(stats[folder]['files']),args.maxcompression], dtype=object)
# Remove empty (no .wav) folders from list
for folder in remove:
folders.remove(folder)
totalfiles = []
for folder in folders:
totalfiles += stats[folder]['files']
totalfiles = len(totalfiles)
if totalfiles == 0:
raise RuntimeError('No .wav files found!')
totalcycles = totalfiles * args.cycles * args.maxcompression
counter_cycles = 0
time_start = datetime.strptime(str(datetime.now()), "%Y-%m-%d %H:%M:%S.%f")
for folder in folders:
# i: 0..Nfiles
# n: 0..8
files = stats[folder]['files']
for i in range(len(files)):
infile = '{}/{}'.format(mypath+folder,files[i])
for n in range(args.maxcompression):
Dtime = []
for j in range(args.cycles):
time1 = datetime.strptime(str(datetime.now()), "%Y-%m-%d %H:%M:%S.%f")
subprocess.run(['flac', infile, '--compression-level-{}'.format(n),
'-f', '-o', '{}/flac{}.flac'.format(temp_folder,n)])
time2 = datetime.strptime(str(datetime.now()), "%Y-%m-%d %H:%M:%S.%f")
Dtime.append((time2-time1).total_seconds())
counter_cycles += 1
# Percentage of totalcycles
status = counter_cycles/totalcycles
remain_factor = (1 - status)/status
time_current = datetime.strptime(str(datetime.now()), "%Y-%m-%d %H:%M:%S.%f")
time_elapsed = (time_current - time_start).total_seconds()
print('========================================')
print('Status: {} %'.format(int(100*status)))
print('Estimated remaining time: {}'.format(str(timedelta(seconds=int(remain_factor * time_elapsed)))))
print('========================================')
Dtime = np.mean(Dtime)
size = os.path.getsize('{}/flac{}.flac'.format(temp_folder,n))
# Array if size (regarded as constat) and mean compression time
# (file1, FLAC0)(file1, FLAC1)...(file1, FLACmaxcompression)
# (file2, FLAC0)(file2, FLAC1)...(file2, FLACmaxcompression)
# ...
stats[folder]['stats'][i,n] = (size, Dtime)
for folder in folders:
# Taking columnwise (for each compression level) means of size...
stats[folder]['ploty_size'] = [np.mean([e[0] for e in stats[folder]['stats'][:,col]])
for col in range(np.shape(stats[folder]['stats'])[1])]
# (relative to FLAC-0)
stats[folder]['ploty_size'] = [i/stats[folder]['ploty_size'][0] for i in stats[folder]['ploty_size']]
# ... and mean time.
stats[folder]['ploty_time'] = [np.mean([e[1] for e in stats[folder]['stats'][:,col]])
for col in range(np.shape(stats[folder]['stats'])[1])]
# (relative to FLAC-0)
stats[folder]['ploty_time'] = [i/stats[folder]['ploty_time'][0] for i in stats[folder]['ploty_time']]
# Rough 'effectivity' estimation -size*time + 2
# Expl.: Starts at (0,1), therefore flipping with (-1) requires
# + 2. Without (-1) would be 'badness'
stats[folder]['ploty_eff'] = [ 2 + (-1) * stats[folder]['ploty_size'][i] * stats[folder]['ploty_time'][i]
for i in range(len(stats[folder]['ploty_size']))]
with open(args.outfile, 'wb') as of:
data = {}
data['stats'] = stats
data['folders'] = folders
data['cycles'] = args.cycles
data['maxcompression'] = args.maxcompression
pkl.dump(data, of, protocol=pkl.HIGHEST_PROTOCOL)
if os.path.isdir(temp_folder): subprocess.run(['rm', '-r', temp_folder])
else:
with open(args.infile, 'rb') as f:
data = pkl.load(f)
stats = data['stats']
folders = data['folders']
args.maxcompression = data['maxcompression']
args.cycles = data['cycles']
fig = plt.figure()
plotx = range(args.maxcompression)
pos = range(len(plotx))
ax_size = fig.add_subplot(111)
ax_size.set_xticks(pos)
ax_size.set_xticklabels(plotx)
ax_size.set_title('FLAC compression comparison')
ax_time = ax_size.twinx()
ax_efficiency = ax_size.twinx()
colorfracs = [i / (len(folders)-0.9) if i > 0 else 0 for i in range(len(folders))]
# Actual plotting
lns = []
for cfrac, folder in zip(colorfracs, folders):
color = plt.cm.viridis(cfrac)
l_size, = ax_size.plot(plotx, stats[folder]['ploty_size'],
color=color, linestyle=':',
label='Size Ratio: {}'.format(folder))
l_time, = ax_time.plot(plotx, stats[folder]['ploty_time'],
color=color, linestyle='--',
label='Time Ratio: {}'.format(folder))
l_eff, = ax_efficiency.plot(plotx, stats[folder]['ploty_eff'],
color=color, linestyle='-',
label='Efficiency: {}'.format(folder))
lns.append(l_size)
lns.append(l_time)
lns.append(l_eff)
ax_efficiency.spines['right'].set_position(('outward', 60))
ax_size.xaxis.grid(color='.85', linestyle='-', linewidth=.5)
ax_size.set_xlabel(xlabel)
ax_size.set_ylabel(ylabel_size)
ax_efficiency.set_ylabel(ylabel_efficiency)
ax_time.set_ylabel(ylabel_time)
lgd = ax_time.legend(handles=lns, loc='upper center',
bbox_to_anchor=(0.5, -.15), facecolor='#FFFFFF',
prop={'family': 'monospace','size': 'small'})
fig.savefig(args.outfile, bbox_inches='tight', dpi=300)