HydrogenAudio

Lossless Audio Compression => FLAC => Topic started by: Axon on 2012-11-25 03:39:49

Title: FLAC bitrate histogram (shell script)
Post by: Axon on 2012-11-25 03:39:49
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.

Code: [Select]
#!/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.
Title: FLAC bitrate histogram (shell script)
Post by: skamp on 2012-11-25 09:02:47
Nice 
Title: FLAC bitrate histogram (shell script)
Post by: Porcus on 2012-11-25 12:48:38
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.)
Title: FLAC bitrate histogram (shell script)
Post by: romor on 2012-11-25 15:37:53
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:
Code: [Select]
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
Code: [Select]
' 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