Jump to content

[UDF] Retrieve MP3 information


Lazycat
 Share

Recommended Posts

This is a rather raw version of UDF. Currently it's doesn't understand MPEG2 and MPEG2.5 files (need to add lookups for different types), but should work with most usual MP3's.

I'm tried to do something with the script in this topic. Although some ideas are usefull, it seems a bit hard to understand and realize by this way :ph34r:

Anyway, if you need it - please test it.

Edited:

Old code removed, latest version here: http://www.autoitscript.com/fileman/users/public/Kot/get_mp3_info.zip

Edited by Kot
Link to comment
Share on other sites

Hi Kot,

this is cool and if at least the TrackLengh Calculation will work that

would be fantastic. Only to calculate like in the VB script is not that

problem (me think), but what the hell is the mp3_pad ???

Greetz

Cape-City

Edited by Cape-City
Link to comment
Share on other sites

this is cool and if at least the TrackLengh Calculation will work that

would be fantastic. Only to calculate like in the vb script: now it should support any MP3 (even VBR ones). But now I have not idea how to get length of VBR file.

$file = FileOpenDialog("Please select file", "", "MP3 files (*.mp3)");
if @error then Exit
$aData = _GetMP3Info($file)
if @error then Exit
$aLabel = StringSplit("MPEG Version: |Layer: |Bitrate: |Frequency: |Channel Mode: |Have CRC: |Copyright: |Original: ", "|")
Dim $out = ""
for $i = 0 to UBound($aData)-1
    $out = $out & $aLabel[$i+1] & $aData[$i] & @CR
next
MsgBox (0, "MP3 Data", $out)

;===============================================================================
;
; Description:    Retrieve MP3 basic information
; Parameter(s):  File name
; Requirement(s):   None
; Return Value(s):  On Success - array with data:
;                      0 - MPEG version
;                      1 - Layer
;                      2 - Bitrate
;                      3 - Frequency
;                      4 - Channel Mode
;                      5 - CRC protected
;                      6 - Copyrighted
;                      7 - Original
;                  On Failure empty string and sets @ERROR:
;                      1 - Info not found
; Author(s):        YDY (Kot) <mpc@nm.ru>
; Version:        1.0.00
; Date:          22.08.2004
; Note(s):        None
;
;===============================================================================

Func _GetMP3Info($file)
Local $frame = "", $data[8], $offset = 1, $isVBR = 0
Local $aVersion = StringSplit("MPEG 2.5|Undefined|MPEG 2.5|MPEG 1", "|")
Local $aLayer = StringSplit("Undefined|Layer III|Layer II|Layer I", "|")
Local $sBitrate = ""
Local $sFrequency = ""
Local $aChanMode = StringSplit("Stereo|Joint stereo|Dual channel|Mono", "|")
Local $aFlags = StringSplit("No|Yes", "|")

If _FileReadAtOffsetHEX ($file, 1, 3) = "494433" Then; ID3v2 tag found
    $offset = BitShift(Dec(_FileReadAtOffsetHEX ($file, 7, 4)), 1); End of ID3v2 tag
Endif

For $ic = $offset to 2048 + $offset
    $marker = _FileReadAtOffsetHEX ($file, $ic, 2)
    $marker = StringLeft($marker, 3)
    If StringInStr("FFF,FFE", $marker) Then; Assume that is frame start
        $frame = _FileReadAtOffsetHEX ($file, $ic, 4)
        If _FileReadAtOffsetHEX ($file, $ic+36, 4) = "58696E67" Then $isVBR = 1; This is a right way?..
        Exitloop
    Endif
Next

If $frame = "" Then 
    SetError(1); Frame not found (not mp3 data?)
    Return ("")
Endif

Local $head = _HexToBin($frame)
Local $nVer = _GetRBits($head, 19, 2)
Local $nLay = _GetRBits($head, 17, 2)

Select
    Case $nVer = 3
        $sFrequency = "44100|48000|32000|Undefined"
    Case $nVer = 2
        $sFrequency = "22050|24000|16000|Undefined"
    Case $nVer = 0
        $sFrequency = "11025|12000|8000|Undefined"
EndSelect
Local $aFrequency = StringSplit($sFrequency, "|")

If $isVBR Then
    $data[2] = "Variable"
Else
    Select
        Case $nVer = 3 and $nLay = 3
            $sBitrate = "Free|32|64|96|128|160|192|224|256|288|320|352|384|416|448|Undefined"
        Case $nVer = 3 and $nLay = 2
            $sBitrate = "Free|32|48|56|64|80|96|112|128|160|192|224|256|320|384|Undefined"
        Case $nVer = 3 and $nLay = 1
            $sBitrate = "Free|32|40|48|56|64|80|96|112|128|160|192|224|256|320|Undefined"
        Case $nVer = 2 and $nLay = 3
            $sBitrate = "Free|32|48|56|64|80|96|112|128|144|160|176|192|224|256|Undefined"
        Case ($nVer = 2 and $nLay = 2) or ($nVer = 2 and $nLay = 1)
            $sBitrate = "Free|8|16|24|32|40|48|56|64|80|96|112|128|144|160|Undefined"
    EndSelect
    Local $aBitrate = StringSplit($sBitrate, "|")
    $data[2] = _GetData($aBitrate, _GetRBits($head, 12, 4))
Endif

$data[0] = _GetData($aVersion, $nVer)
$data[1] = _GetData($aLayer, $nLay)
$data[3] = _GetData($aFrequency, _GetRBits($head, 10, 2))
$data[4] = _GetData($aChanMode, _GetRBits($head, 6, 2))
$data[5] = _GetData($aFlags, not _GetRBits($head, 16, 1)); CRC
$data[6] = _GetData($aFlags, _GetRBits($head, 3, 1)); Private
$data[7] = _GetData($aFlags, _GetRBits($head, 2, 1)); Original
Return($data) 
EndFunc

Func _GetRBits($str, $pos, $size)
    Local $ic, $res = 0, $bStr = StringMid($str, 33 - $pos - $size, $size)
    For $ic = 0 to $size-1
        If StringMid($bStr, $size-$ic, 1) == "1" Then $res = $res + 2^$ic
    Next
    Return ($res)
EndFunc

Func _GetData(ByRef $array, $val)
    If $val > UBound($array)-1 Then Return("Undefined")
    Return ($array[$val+1])
EndFunc

Func _HexToBin($str)
Local $res=""
While StringLen($str) > 0
    $val = Dec(StringLeft($str, 1))
    $str = StringTrimLeft($str, 1)
    For $i = 3 to 0 Step -1
        $pw = 2^$i
        If $val - $pw < 0 Then 
            $res = $res & "0"
        Else
            $res = $res & "1"
            $val = $val - $pw
        Endif
    Next
Wend
Return ($res)
EndFunc

Func _FileReadAtOffsetHEX ($file, $offset, $bytes)
    Local $tfile = FileOpen($file, 0)
    Local $tstr = ""
    FileRead($tfile, $offset-1)
    For $i = $offset To $offset + $bytes - 1
        $tstr =  $tstr & Hex(Asc(FileRead($tfile, 1)), 2)
    Next
    FileClose($tfile)
    Return ($tstr)
Endfunc
Link to comment
Share on other sites

Hi,

when i add the calculation from the VB script, it not exatly shows the

duration, but it's more then nothing :

$file = FileOpenDialog("Please select file", "", "MP3 files (*.mp3)");
if @error then Exit
$Filesize = FileGetSize($file)
$aData = _GetMP3Info($file)
if @error then Exit
$aLabel = StringSplit("MPEG Version: |Layer: |Bitrate: |Frequency: |Channel Mode: |Have CRC: |Copyright: |Original: |Duration (ms): ", "|")
Dim $out = ""
for $i = 0 to UBound($aData)-1
    $out = $out & $aLabel[$i+1] & $aData[$i] & @CR
next
MsgBox (0, "MP3 Data", $out)

;===============================================================================
;
; Description:    Retrieve MP3 basic information
; Parameter(s):  File name
; Requirement(s):   None
; Return Value(s):  On Success - array with data:
;                      0 - MPEG version
;                      1 - Layer
;                      2 - Bitrate
;                      3 - Frequency
;                      4 - Channel Mode
;                      5 - CRC protected
;                      6 - Copyrighted
;                      7 - Original
;                       8 - Duration
;                  On Failure empty string and sets @ERROR:
;                      1 - Info not found
; Author(s):        YDY (Kot) <mpc@nm.ru>
; Version:        1.0.00
; Date:          22.08.2004
; Note(s):        None
;
;===============================================================================

Func _GetMP3Info($file)
Local $frame = "", $data[9], $offset = 1, $isVBR = 0
Local $aVersion = StringSplit("MPEG 2.5|Undefined|MPEG 2.5|MPEG 1", "|")
Local $aLayer = StringSplit("Undefined|Layer III|Layer II|Layer I", "|")
Local $sBitrate = ""
Local $sFrequency = ""
Local $aChanMode = StringSplit("Stereo|Joint stereo|Dual channel|Mono", "|")
Local $aFlags = StringSplit("No|Yes", "|")

If _FileReadAtOffsetHEX ($file, 1, 3) = "494433" Then; ID3v2 tag found
    $offset = BitShift(Dec(_FileReadAtOffsetHEX ($file, 7, 4)), 1); End of ID3v2 tag
Endif

For $ic = $offset to 2048 + $offset
    $marker = _FileReadAtOffsetHEX ($file, $ic, 2)
    $marker = StringLeft($marker, 3)
    If StringInStr("FFF,FFE", $marker) Then; Assume that is frame start
        $frame = _FileReadAtOffsetHEX ($file, $ic, 4)
        If _FileReadAtOffsetHEX ($file, $ic+36, 4) = "58696E67" Then $isVBR = 1; This is a right way?..
        Exitloop
    Endif
Next

If $frame = "" Then 
    SetError(1); Frame not found (not mp3 data?)
    Return ("")
Endif

Local $head = _HexToBin($frame)
Local $nVer = _GetRBits($head, 19, 2)
Local $nLay = _GetRBits($head, 17, 2)

Select
    Case $nVer = 3
        $sFrequency = "44100|48000|32000|Undefined"
    Case $nVer = 2
        $sFrequency = "22050|24000|16000|Undefined"
    Case $nVer = 0
        $sFrequency = "11025|12000|8000|Undefined"
EndSelect
Local $aFrequency = StringSplit($sFrequency, "|")

If $isVBR Then
    $data[2] = "Variable"
Else
    Select
        Case $nVer = 3 and $nLay = 3
            $sBitrate = "Free|32|64|96|128|160|192|224|256|288|320|352|384|416|448|Undefined"
        Case $nVer = 3 and $nLay = 2
            $sBitrate = "Free|32|48|56|64|80|96|112|128|160|192|224|256|320|384|Undefined"
        Case $nVer = 3 and $nLay = 1
            $sBitrate = "Free|32|40|48|56|64|80|96|112|128|160|192|224|256|320|Undefined"
        Case $nVer = 2 and $nLay = 3
            $sBitrate = "Free|32|48|56|64|80|96|112|128|144|160|176|192|224|256|Undefined"
        Case ($nVer = 2 and $nLay = 2) or ($nVer = 2 and $nLay = 1)
            $sBitrate = "Free|8|16|24|32|40|48|56|64|80|96|112|128|144|160|Undefined"
    EndSelect
    Local $aBitrate = StringSplit($sBitrate, "|")
    $data[2] = _GetData($aBitrate, _GetRBits($head, 12, 4))
Endif

$data[0] = _GetData($aVersion, $nVer)
$data[1] = _GetData($aLayer, $nLay)
$data[3] = _GetData($aFrequency, _GetRBits($head, 10, 2))
$data[4] = _GetData($aChanMode, _GetRBits($head, 6, 2))
$data[5] = _GetData($aFlags, not _GetRBits($head, 16, 1)); CRC
$data[6] = _GetData($aFlags, _GetRBits($head, 3, 1)); Private
$data[7] = _GetData($aFlags, _GetRBits($head, 2, 1)); Original

$Framesize = ((144 * $data[2]) / $data[3])
$Total_frames = $Filesize / $Framesize
$Track_length = ($Total_frames / 38.5); / 1000 (to get sec)

$data[8] = $Track_length; / 60 (to get min)

Return($data) 
EndFunc

Func _GetRBits($str, $pos, $size)
    Local $ic, $res = 0, $bStr = StringMid($str, 33 - $pos - $size, $size)
    For $ic = 0 to $size-1
        If StringMid($bStr, $size-$ic, 1) == "1" Then $res = $res + 2^$ic
    Next
    Return ($res)
EndFunc

Func _GetData(ByRef $array, $val)
    If $val > UBound($array)-1 Then Return("Undefined")
    Return ($array[$val+1])
EndFunc

Func _HexToBin($str)
Local $res=""
While StringLen($str) > 0
    $val = Dec(StringLeft($str, 1))
    $str = StringTrimLeft($str, 1)
    For $i = 3 to 0 Step -1
        $pw = 2^$i
        If $val - $pw < 0 Then 
            $res = $res & "0"
        Else
            $res = $res & "1"
            $val = $val - $pw
        Endif
    Next
Wend
Return ($res)
EndFunc

Func _FileReadAtOffsetHEX ($file, $offset, $bytes)
    Local $tfile = FileOpen($file, 0)
    Local $tstr = ""
    FileRead($tfile, $offset-1)
    For $i = $offset To $offset + $bytes - 1
        $tstr =  $tstr & Hex(Asc(FileRead($tfile, 1)), 2)
    Next
    FileClose($tfile)
    Return ($tstr)
Endfunc

Greetz

Cape-City

Link to comment
Share on other sites

when i add the calculation from the VB script, it not exatly shows the

duration, but it's more then nothing :

I'm suspect that calculation is very rough (what the strange divisor 38.5?) - it usually give 1-3 seconds error. Try new version: seems this is more accurate.

$file = FileOpenDialog("Please select file", "", "MPEG files (*.mp3;*.mp2;*.mpa)");
if @error then Exit
$aData = _GetMP3Info($file)
if @error then Exit
$aLabel = StringSplit("MPEG Version: |Layer: |Bitrate: |Frequency: |Channel Mode: |Duration: |Frames: |Have CRC: |Copyright: |Original: ", "|")
Dim $out = ""
for $i = 0 to UBound($aData)-1
    $out = $out & $aLabel[$i+1] & $aData[$i] & @CR
next
MsgBox (0, "MPEG File Info", $out)

;===============================================================================
;
; Description:      Retrieve MP3 (MP2, MPA) basic information
; Parameter(s):     File name
; Requirement(s):   None
; Return Value(s):  On Success - array with data:
;                       0 - MPEG version
;                       1 - Layer
;                       2 - Bitrate
;                       3 - Frequency
;                       4 - Channel Mode
;                       5 - Duration
;                       6 - Frames
;                       7 - CRC protected
;                       8 - Copyrighted
;                       9 - Original
;                   On Failure empty string and sets @ERROR:
;                       1 - Info not found
; Author(s):        YDY (Kot) <mpc@nm.ru>
; Version:          1.0.00
; Date:             22.08.2004
; Note(s):          None
;
;===============================================================================

Func _GetMP3Info($file)
Local $frame = "", $data[10], $offset = 1, $isVBR = 0
Local $aVersion = StringSplit("MPEG 2.5|Undefined|MPEG 2.5|MPEG 1", "|")
Local $aLayer = StringSplit("Undefined|Layer III|Layer II|Layer I", "|")
Local $sBitrate = ""
Local $sFrequency = ""
Local $aChanMode = StringSplit("Stereo|Joint stereo|Dual channel|Mono", "|")
Local $aFlags = StringSplit("No|Yes", "|")

If _FileReadAtOffsetHEX ($file, 1, 3) = "494433" Then; ID3v2 tag found
    $offset = BitShift(Dec(_FileReadAtOffsetHEX ($file, 7, 4)), 1); End of ID3v2 tag
Endif

For $ic = $offset to 2048 + $offset
    $marker = _FileReadAtOffsetHEX ($file, $ic, 2)
    $marker = StringLeft($marker, 3)
    If StringInStr("FFF,FFE", $marker) Then; Assume that is frame start
        $frame = _FileReadAtOffsetHEX ($file, $ic, 4)
        If _FileReadAtOffsetHEX ($file, $ic+36, 4) = "58696E67" Then $isVBR = 1; Is this a right way?..
        Exitloop
    Endif
Next

If $frame = "" Then 
    SetError(1); Frame not found (not mp3 data?)
    Return ("")
Endif

Local $head = _HexToBin($frame)
Local $nVer = _GetRBits($head, 19, 2)
Local $nLay = _GetRBits($head, 17, 2)

Select
    Case $nVer = 3
        $sFrequency = "44100|48000|32000|Undefined"
    Case $nVer = 2
        $sFrequency = "22050|24000|16000|Undefined"
    Case $nVer = 0
        $sFrequency = "11025|12000|8000|Undefined"
EndSelect
Local $aFrequency = StringSplit($sFrequency, "|")
$data[3] = _GetData($aFrequency, _GetRBits($head, 10, 2))

Local $pad = 0, $bitrate, $framesize, $frames, $length, $fps
If _GetRBits($head, 9, 1) Then $pad = 1

If $isVBR Then
    $data[2] = "Variable"
Else
    Select
        Case $nVer = 3 and $nLay = 3
            $sBitrate = "Free|32|64|96|128|160|192|224|256|288|320|352|384|416|448|Undefined"
        Case $nVer = 3 and $nLay = 2
            $sBitrate = "Free|32|48|56|64|80|96|112|128|160|192|224|256|320|384|Undefined"
        Case $nVer = 3 and $nLay = 1
            $sBitrate = "Free|32|40|48|56|64|80|96|112|128|160|192|224|256|320|Undefined"
        Case $nVer = 2 and $nLay = 3
            $sBitrate = "Free|32|48|56|64|80|96|112|128|144|160|176|192|224|256|Undefined"
        Case ($nVer = 2 and $nLay = 2) or ($nVer = 2 and $nLay = 1)
            $sBitrate = "Free|8|16|24|32|40|48|56|64|80|96|112|128|144|160|Undefined"
    EndSelect
    Local $aBitrate = StringSplit($sBitrate, "|")
    $data[2] = _GetData($aBitrate, _GetRBits($head, 12, 4))
    $bitrate = 1000 * $data[2]
    If $nLay = 3 Then 
        $framesize = (((12 * $bitrate) / $data[3]) + $pad) * 4
        $fps = $data[3]/384
    Else
        $framesize = ((144 * $bitrate) / $data[3]) + $pad
        $fps = $data[3]/1152
    Endif
    $frames = FileGetSize($file) / $framesize
    $length = $frames / $fps
Endif

$data[0] = _GetData($aVersion, $nVer)
$data[1] = _GetData($aLayer, $nLay)
$data[4] = _GetData($aChanMode, _GetRBits($head, 6, 2))
$data[5] = StringFormat("%d:%02d", Int($length / 60), $length - Int($length / 60) * 60)
$data[6] = Int($frames)
$data[7] = _GetData($aFlags, not _GetRBits($head, 16, 1)); CRC
$data[8] = _GetData($aFlags, _GetRBits($head, 3, 1)); Private
$data[9] = _GetData($aFlags, _GetRBits($head, 2, 1)); Original
Return($data) 
EndFunc

Func _GetRBits($str, $pos, $size)
    Local $ic, $res = 0, $bStr = StringMid($str, 33 - $pos - $size, $size)
    For $ic = 0 to $size-1
        If StringMid($bStr, $size-$ic, 1) == "1" Then $res = $res + 2^$ic
    Next
    Return ($res)
EndFunc

Func _GetData(ByRef $array, $val)
    If $val > UBound($array)-1 Then Return("Undefined")
    Return ($array[$val+1])
EndFunc

Func _HexToBin($str)
Local $res=""
While StringLen($str) > 0
    $val = Dec(StringLeft($str, 1))
    $str = StringTrimLeft($str, 1)
    For $i = 3 to 0 Step -1
        $pw = 2^$i
        If $val - $pw < 0 Then 
            $res = $res & "0"
        Else
            $res = $res & "1"
            $val = $val - $pw
        Endif
    Next
Wend
Return ($res)
EndFunc

Func _FileReadAtOffsetHEX ($file, $offset, $bytes)
    Local $tfile = FileOpen($file, 0)
    Local $tstr = ""
    FileRead($tfile, $offset-1)
    For $i = $offset To $offset + $bytes - 1
        $tstr =  $tstr & Hex(Asc(FileRead($tfile, 1)), 2)
    Next
    FileClose($tfile)
    Return ($tstr)
Endfunc
Edited by Kot
Link to comment
Share on other sites

Hi,

I got some errors with a few MP3 files, here the info out of winamp :

Size: 3593219 bytes

Header found at: 1723 bytes

Length: 64 seconds

MPEG 1.0 layer 1

448kbit, 1781 frames

32000Hz Stereo

CRCs: No

Copyrighted: No

Original: No

Emphasis: invalid

--> Duration : Unidentified

Size: 3477504 bytes

Header found at: 3127 bytes

Length: 217 seconds

MPEG 1.0 layer 3

128kbit, 8334 frames

44100Hz Stereo

CRCs: No

Copyrighted: No

Original: Yes

Emphasis: None

U're script end with error in this function :

Func _GetData(ByRef $array, $val)

If $val > UBound($array)-1 Then Return("Undefined")

Return ($array[$val+1])

EndFunc

Error: Array variable has incorrect number of substrings or subscript

dimension range exceeded.

Greetz

Cape-City

Link to comment
Share on other sites

Hmm... Maybe input value happened negative?.. Try to change this function with following code:

Func _GetData(ByRef $array, $val)
    If ($val > UBound($array)-1) or ($val < 0) Then Return("Undefined")
    Return ($array[$val+1])
EndFunc
Link to comment
Share on other sites

Hi,

I tried this :

Func _GetData(ByRef $array, $val)

If ($val > UBound($array)-1) or ($val < 0) Then Return("Undefined")

MsgBox(0, "Val :", $val)

Return ($array[$val+1])

EndFunc

On a working MP3 I get e.g. this values :

0,9,3,1,0,0,0,1 or 3,15,3,3,3,0,1,1 or 0,9,3,1,0,0,1

At the special MP3 I got :

1

and then the script crashed ... Seems he had a prob with $array[2] ???

Greetz

Cape-City

Edited by Cape-City
Link to comment
Share on other sites

Thanks, I have revisited code.

Strange file, the first frames after v2 tag looks like normal, but contain wrong info (frame corrupt?) I put new check into frame search loop, so now it will search until first correct (possibly) frame appear. Now limit of search distance is 4096 bytes, after that error will be return.

I'm not sure that checking only MPEG version and layer will be enough for conclusion that frame is correct or corrupt, so please test it.

New version is here: http://www.autoitscript.com/fileman/users/public/Kot/get_mp3_info.zip

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...