Created
May 8, 2016 18:28
-
-
Save kommen/9391eb6391d76c2b42c0402fc5ca7353 to your computer and use it in GitHub Desktop.
Compute MP3 variable bitrate file duration with Elixir
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Mp3Info do | |
def duration(data) do | |
compute_duration(data, 0, 0) | |
end | |
defp compute_duration(data, offset, duration) when byte_size(data) > offset do | |
case decode_header_at_offset(offset, data) do | |
{:ok, length, {samplerate, samples}} -> | |
compute_duration(data, offset + length, duration + (samples / samplerate)) | |
_ -> | |
compute_duration(data, offset + 1, duration) | |
end | |
end | |
defp compute_duration(_data, _offset, duration) do | |
Float.round(duration, 3) | |
end | |
defp decode_header_at_offset(offset, data) do | |
try do | |
binary_part(data, offset, 4) | |
|> decode_header | |
rescue | |
ArgumentError -> {:error, "not enough bytes"} | |
end | |
end | |
defp decode_header(<<255, 7::size(3), | |
audio_version_id :: size(2), layer_desc :: size(2), _d :: size(1), bitrate_index :: size(4), | |
sampling_rate_index :: size(2), padding :: size(1), bits :: size(9)>>) do | |
with {:ok, version} <- audio_version(audio_version_id), | |
{:ok, layer} <- layer(layer_desc), | |
{:ok, bitrate} <- bitrate(version, layer, bitrate_index), | |
{:ok, samplerate} <- samplerate(version, sampling_rate_index), | |
{:ok, samples} <- samples(version, layer), | |
{:ok, framelength} <- framelength(layer, bitrate, samplerate, padding), | |
do: {:ok, framelength, {samplerate, samples}} | |
end | |
defp decode_header(_a) do | |
{:error, :invalid_header} | |
end | |
defp framelength(v, br, sr, p) do | |
fl = compute_framelength(v, br, sr, p) | |
if fl < 27 do | |
{:error, "frame too short"} | |
else | |
{:ok, fl} | |
end | |
end | |
defp compute_framelength(1, bitrate, samplerate, padding) do | |
trunc(((12 * bitrate / samplerate) + padding) * 4) | |
end | |
defp compute_framelength(_, bitrate, samplerate, padding) do | |
trunc((144 * bitrate / samplerate) + padding) | |
end | |
defp audio_version(audio_version_id) do | |
case audio_version_id do | |
0 -> {:ok, {2, 5}} | |
2 -> {:ok, 2} | |
3 -> {:ok, 1} | |
1 -> {:error, "bad version"} | |
end | |
end | |
defp layer(layer_desc) do | |
case layer_desc do | |
0 -> {:error, "bad layer"} | |
1 -> {:ok, 3} | |
2 -> {:ok, 2} | |
3 -> {:ok, 1} | |
end | |
end | |
defp bitrate(_, _, 15), do: {:error, "bad bitrate"} | |
defp bitrate(v, l, e), do: {:ok, bitrate_map(v, l, e) * 1000} | |
defp bitrate_map(1, 1, e), do: elem({0,32,64,96,128,160,192,224,256,288,320,352,384,416,448}, e) | |
defp bitrate_map(1, 2, e), do: elem({0,32,48,56,64,80,96,112,128,160,192,224,256,320,384}, e) | |
defp bitrate_map(1, 3, e), do: elem({0,32,40,48,56,64,80,96,112,128,160,192,224,256,320}, e) | |
defp bitrate_map(2, 1, e), do: elem({0,32,48,56,64,80,96,112,128,144,160,176,192,224,256}, e) | |
defp bitrate_map(2, 2, e), do: elem({0,8,16,24,32,40,48,56,64,80,96,112,128,144,160}, e) | |
defp bitrate_map(2, 3, e), do: bitrate_map(2, 2, e) | |
defp bitrate_map({2,5}, l, e), do: bitrate_map(2, l, e) | |
defp samplerate(1, 0), do: {:ok, 44100} | |
defp samplerate(1, 0), do: {:ok, 44100} | |
defp samplerate(1, 1), do: {:ok, 48000} | |
defp samplerate(1, 2), do: {:ok, 32000} | |
defp samplerate(2, 0), do: {:ok, 22050} | |
defp samplerate(2, 1), do: {:ok, 24000} | |
defp samplerate(2, 2), do: {:ok, 16000} | |
defp samplerate({2,5}, 0), do: {:ok, 11025} | |
defp samplerate({2,5}, 1), do: {:ok, 12000} | |
defp samplerate({2,5}, 2), do: {:ok, 8000} | |
defp samplerate(_, _), do: {:error, "bad samplerate"} | |
defp samples(1, 1), do: {:ok, 384} | |
defp samples(1, 2), do: {:ok, 1152} | |
defp samples(1, 3), do: {:ok, 1152} | |
defp samples(2, 1), do: {:ok, 384} | |
defp samples(2, 2), do: {:ok, 1152} | |
defp samples(2, 3), do: {:ok, 576} | |
defp samples({2,5}, l), do: samples(2, l) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment