From 1fc60aae2aa156acc80128d4cc6516286e92c8f3 Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Fri, 12 Nov 2021 21:59:29 -0500 Subject: [PATCH] =?UTF-8?q?Replace=20ffmpeg=20with=20elixir=20mp3=20decode?= =?UTF-8?q?r=20=F0=9F=94=A5=F0=9F=94=A5=F0=9F=94=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/live_beats/mp3_stat.ex | 376 ++++++++++++++++++++++++++++++++++--- 1 file changed, 350 insertions(+), 26 deletions(-) diff --git a/lib/live_beats/mp3_stat.ex b/lib/live_beats/mp3_stat.ex index 984d946..7e059d1 100644 --- a/lib/live_beats/mp3_stat.ex +++ b/lib/live_beats/mp3_stat.ex @@ -1,12 +1,32 @@ defmodule LiveBeats.MP3Stat do + @moduledoc """ + Decodes MP3s and parses out information. + + MP3 decoding and duration calculation credit to: + https://shadowfacts.net/2021/mp3-duration/ + """ + use Bitwise alias LiveBeats.MP3Stat defstruct duration: 0, path: nil + @declared_frame_ids ~w(AENC APIC ASPI COMM COMR ENCR EQU2 ETCO GEOB GRID LINK MCDI MLLT OWNE PRIV PCNT POPM POSS RBUF RVA2 RVRB SEEK SIGN SYLT SYTC TALB TBPM TCOM TCON TCOP TDEN TDLY TDOR TDRC TDRL TDTG TENC TEXT TFLT TIPL TIT1 TIT2 TIT3 TKEY TLAN TLEN TMCL TMED TMOO TOAL TOFN TOLY TOPE TOWN TPE1 TPE2 TPE3 TPE4 TPOS TPRO TPUB TRCK TRSN TRSO TSOA TSOP TSOT TSRC TSSE TSST TXXX UFID USER USLT WCOM WCOP WOAF WOAR WOAS WORS WPAY WPUB WXXX) + + @v1_l1_bitrates {:invalid, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, + :invalid} + @v1_l2_bitrates {:invalid, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, + :invalid} + @v1_l3_bitrates {:invalid, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, + :invalid} + @v2_l1_bitrates {:invalid, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, + :invalid} + @v2_l2_l3_bitrates {:invalid, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, + :invalid} + def to_mmss(duration) when is_integer(duration) do hours = div(duration, 60 * 60) - minutes = div(duration - (hours * 60 * 60), 60) - seconds = rem(duration - (hours * 60 * 60) - (minutes * 60), 60) + minutes = div(duration - hours * 60 * 60, 60) + seconds = rem(duration - hours * 60 * 60 - minutes * 60, 60) [minutes, seconds] |> Enum.map(fn count -> String.pad_leading("#{count}", 2, ["0"]) end) @@ -14,33 +34,337 @@ defmodule LiveBeats.MP3Stat do end def parse(path) do - args = ["-v", "quiet", "-stats", "-i", path, "-f", "null", "-"] - - # "size=N/A time=00:03:00.00 bitrate=N/A speed= 674x" - case System.cmd("ffmpeg", args, stderr_to_stdout: true) do - {output, 0} -> parse_output(output, path) - {_, 1} -> {:error, :bad_file} - other -> {:error, other} - end + {%{} = _tag_info, rest} = parse_tag(File.read!(path)) + duration = parse_frame(rest, 0, 0, 0) + {:ok, %MP3Stat{duration: round(duration), path: path}} + rescue + _ -> {:error, :bad_file} end - defp parse_output(output, path) do - with %{"time" => time} <- Regex.named_captures(~r/.*time=(?