In the meantime, does anyone know of a program that displays a histogram of bitrates from flac and hopefully some of the other more popular lossless formats?
No, but I vaguely recall wanting precisely this sort of information before. It's a holiday weekend, and I was bored, so I OCD'd out and hacked this together. Obviously, this probably requires a Unix system of some sort.
#!/bin/bash
set -e
if [ ! -f "$1" -o $# != "2" ]; then
echo "Usage: $0 <flac> <histogram-png>" >&2
exit 1
fi
FLAC_PATH=$1
FLAC_FILE=`basename "$FLAC_PATH"`
# uncomment this to autodect
#HISTOGRAM=`sed -e 's/\(.*\)\.flac/histogram-\1\.png/' <<< "$FLAC_FILE"`
HISTOGRAM=$2
BITS_ORIG=/tmp/bits-orig.txt
BITS=/tmp/bits.txt
read minblk maxblk origminframe origmaxframe channels bps rate <<< \
`metaflac --show-min-blocksize --show-max-blocksize \
--show-min-framesize --show-max-framesize \
--show-channels --show-bps --show-sample-rate "$FLAC_PATH"`
# $blocksize is as reported by metaflac. Must be constant for entire file
if [ "$minblk" != "$maxblk" ]; then
echo "ERROR: min/max blocksizes differ ($minblk, $maxblk)." >&2
exit 1
fi
blocksize=$minblk
# $rawblocksize: uncompressed size of block, in bits, including all channels
rawblocksize=$(($blocksize*$channels*$bps))
# Analyze FLAC, extract "bits=" property of each frame
flac -sa "$FLAC_PATH" -o- \
| grep "blocksize=$BLOCKSIZE" \
| sed -e 's/.*bits=\([0-9]*\).*/\1/' \
>& $BITS_ORIG
# The "actual" minimum/maximum frame sizes appear to be outliers; e.g. for one
# FLAC with average frame size of roughly 70kb, the very first frame was 203kb
# and was nearly 2x the size of the next largest frame; the 2nd and 3rd frame
# were 112 bits long and were 135x smaller than the next smallest frame. For the
# sake of the histogram, drop those out.
echo "Dropping min frame size $(($origminframe*8))" \
"and first frame size `head -1 $BITS_ORIG`."
tail -n +2 $BITS_ORIG | grep -v "^$(($origminframe*8))$" > $BITS
#cat $BITS_ORIG > $BITS # uncomment this to include min/max frame sizes
read minframesize maxframesize <<< \
`awk -e 'BEGIN { max=0; min=999999; } min > $0 {min=$0} \
max < $0 {max=$0} END { print min " " max }' \
< $BITS`
gnuplot <<EOF
bin(x,width)=width*floor(x/width)
binwidth=25 # in kbits/s
set xlabel "Frame effective bitrate (kbps)"
set ylabel "frequency (frames)"
set xrange [$minbits:$maxbits]
# comment this out to display gnuplot window instead of writing to png
set term png size 500, 300 font "DejaVu Sans,8"
set output "$HISTOGRAM"
set yrange [0.0:]
plot "$BITS" using (bin(\$1/(1.e3*$blocksize/$rate),binwidth)):(1.0) title "$FLAC_FILE" smooth freq with boxes
# if commenting out above png output lines, uncomment this
# pause mouse
EOF
echo "Done, output in $HISTOGRAM"
rm -f $BITS $BITS_ORIG
Sample output...
Tame Impala, "Elephant":
(http://i.imgur.com/ohocA.png)
Merzbow, "I Lead You Towards Glorious Times":
(http://i.imgur.com/Nb3x2.png)
Autechre & Hafler Trio, "æ³o":
(http://i.imgur.com/JuU2a.png)
I'm not aware of any way to get this sort of information from a format-independent utility like sox. Which is unfortunate.
Nice
Yeah nice. If it could also specify file average and 1-second peak?
I have posted this a few timed, but Merzbow: I Lead You Towards Glorious Times is the least-compressible piece of music I have ever come across. FLAC -8 doesn't manage to get it below 1395, which in the very least beats TAK, which even at -p4m returns more than 100 percent of uncompressed filesize. (WavPack extra high saves some 7 percent I think.)
Like it too
Then made Windows version.
I doubt what to do for histogram, then I guess ascii is universal.
It accepts optional parameter for number of bins (default 32), unlike binwidth in Axon's version.
Path to executables should be set according user preferance.
Example:
C:\>cscript flachist.vbs "10 - Hysteria.flac"
Microsoft (R) Windows Script Host Version 5.7
Copyright (C) Microsoft Corporation. All rights reserved.
10 - Hysteria.flac
0: 36>
36: 73>
73: 110>
110: 147>
147: 183>
183: 220>
220: 257>
257: 294>
294: 330>
330: 367>
367: 404>
404: 441> *
441: 478> *
478: 514> *
514: 551> *
551: 588> *
588: 625> *
625: 661> **
661: 698> ***
698: 735> *****
735: 772> **********
772: 808> *************
808: 845> **************
845: 882> ***************************
882: 919> *************************************
919: 956> *******************************************************
956: 992> *************************************************************
992:1029> ********************************************************************
1029:1066> *********************************************************
1066:1103> ************************
1103:1139> ****
1139:1176>
flachist.vbs' Path to executables
flac = "C:\Program Files\foobar2000\tools\flac.exe"
metaflac = "C:\Program Files\foobar2000\tools\metaflac.exe"
Set WshShell = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")
If WScript.Arguments.Count And fso.FileExists(WScript.Arguments(0)) Then
flacArgs = " -asc "
metaflacArgs = " --show-min-blocksize --show-max-blocksize --show-min-framesize" &_
" --show-max-framesize --show-channels --show-bps --show-sample-rate "
q = Chr(34) : flac = q & flac & q : metaflac = q & metaflac & q
Set cmd = WshShell.exec(metaflac & metaflacArgs & q & WScript.Arguments(0) & q)
info = Split(cmd.StdOut.ReadAll(), vbCrLf)
If info(0) <> info(1) Then
WScript.Echo "ERROR: min/max blocksizes differ: " & info(0) & "<>" & info(1)
WScript.Quit
Else
blocksize = info(0) : origminframe = info(2) : rate = info(6)
End If
' Write bits info data to csv file
Set csv = fso.CreateTextFile(WScript.Arguments(0) & ".csv")
Set cmd = WshShell.exec(flac & flacArgs & q & WScript.Arguments(0) & q)
Do While cmd.StdOut.AtEndOfStream <> True
readline = cmd.StdOut.ReadLine
If InStr(readline, "blocksize=" & blocksize) Then
bits = Split(Split(readline, vbTab)(2), "=")(1)
If Int(bits) <> origminframe*8 Then csv.WriteLine(bits)
End If
Loop
' Read csv
Set csv = fso.OpenTextFile(WScript.Arguments(0) & ".csv")
lines = Split(csv.ReadAll(), vbCrLf)
max = 0 : bins = 32
If WScript.Arguments.Count > 1 Then bins = CInt(WScript.Arguments(1))
For Each line In lines
If line <> "" Then If Int(line) > max Then max = Int(line)
Next
' Print ascii histogram
bin = Array() : ReDim bin(bins)
maxbin = 0
For Each line In lines
If line <> "" Then idx = Fix(bins*Int(line)/(max+1)) : bin(idx) = bin(idx) + 1
If bin(idx) > maxbin Then maxbin = bin(idx)
Next
WScript.Echo WScript.Arguments(0) & vbCrLf
m = max*rate/bins/blocksize/1000
For i=0 To bins-1
WScript.Echo Right(" " & Int(i*m), 4) & ":" &_
Right(" " & Int((i+1)*m), 4) & "> " &_
String(Int(bin(i)*68/maxbin), "*")
Next
Else
WScript.Echo "Usage: cscript flachist.vbs flac-file-path [bins]"
End If