jak64 ask for a way to get the duration of a mp3 file... and it is more than just read a value ... So I dig in my sources and .... voila.
This module based on "MP3 Header auslesen" from the german forum (Thorsten1867)
This module get a lot of infos about a mp3 file. Also the ID3Tags. The Duration is sometimes not 100% exact, but who is perfect
Code: Select all
DeclareModule MP3Info
;{ ===========================================================================================================[Header ]
;:
;: Name ......... : Name
;: Type ......... : Module
;: Author ....... : George Bisonte
;: Compiler ..... : PureBasic V5.60
;: Flags ........ : Unicode/XP-Skin/UserMode/ThreadSafe
;: Subsystem .... : none
;: TargetOS ..... : Windows / MacOs / Linux ?
;: Link ......... : none
;: Description .. : Read MP3 Data. Based on Thorsten1867 : https://www.purebasic.fr/german/viewtopic.php?p=356500
;: License ...... : MIT License
;:
;: Permission is hereby granted, free of charge, to any person obtaining a copy
;: of this software and associated documentation files (the "Software"), to deal
;: in the Software without restriction, including without limitation the rights
;: to use, copy, modify, merge, publish, distribute, sublicense, And/Or sell
;: copies of the Software, and to permit persons to whom the Software is
;: furnished to do so, subject to the following conditions:
;:
;: The above copyright notice and this permission notice shall be included in all
;: copies or substantial portions of the Software.
;:
;: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
;: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
;: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
;: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
;: SOFTWARE.
;:
;}
EnableExplicit
Structure mp3_data
ID.i
Layer.i
Protection.i
Bitrate.i
Samplingrate.i
Padding.i
Private.i
Channel.i
Extension.i
Copyright.i
Original.i
Emphasis.i
FrameLength.i
Frames.i
Duration.i
ID3v10.i
ID3v11.i
ID3v2.i
Map ID3Tag.s()
EndStructure
Declare.i GetMP3Info(FileName.s, *mp3_data.mp3_data)
EndDeclareModule
Module MP3Info
Macro uInt8(Value)
Value & $FF
EndMacro
Macro uInt16(Value)
(Value>>8&$FF) | (Value<<8&$FF00)
EndMacro
#MPEG25 = 0 ; MPEG Version 2.5 (later extension of MPEG 2)
#MPEG2 = 2 ; MPEG Version 2 (ISO/IEC 13818-3)
#MPEG1 = 3 ; MPEG Version 1 (ISO/IEC 11172-3)
#Layer1 = 3
#Layer2 = 2
#Layer3 = 1
#Stereo = 0
#Joint = 1 ; Joint stereo (Stereo)
#DualMono = 2 ; Dual channel (2 mono channels)
#Mono = 3 ; Single channel (Mono)
#None = 0
#MS50_15 = 1 ; 50/15 ms
#CCIT_J17 = 3 ; CCIT J.17
Structure ID3TAGV1
TAG.a[3]
Title.a[30]
Artist.a[30]
Album.a[30]
Year.a[4]
Comment.b[30]
Genre.a
EndStructure
Structure ID3TAGV2_HEADER
Identifier.a[3]
Version.a[2]
flags.a
size.b[4]
EndStructure
Structure ID3TAGV2_FRAME
FrameID.a[4]
Size.l
Flags.a[2]
Encoding.a
*Data
EndStructure
Global Dim SamplesPerFrame(3, 3)
Global Dim Genre.s(125)
SamplesPerFrame(#Layer1, #MPEG1) = 384
SamplesPerFrame(#Layer1, #MPEG2) = 384
SamplesPerFrame(#Layer1, #MPEG25) = 384
SamplesPerFrame(#Layer2, #MPEG1) = 1152
SamplesPerFrame(#Layer2, #MPEG2) = 1152
SamplesPerFrame(#Layer2, #MPEG25) = 1152
SamplesPerFrame(#Layer3, #MPEG1) = 1152
SamplesPerFrame(#Layer3, #MPEG2) = 576
SamplesPerFrame(#Layer3, #MPEG25) = 576
;{ From Celtic88 : https://www.purebasic.fr/english/viewtopic.php?p=506795#p506795
Macro SwapDataType(iValue,iReturn)
Protected vSize.b = SizeOf(iReturn)-1,ii.b:For ii= 0 To vSize:PokeB(@iReturn+ii,PeekB(@iValue+(vSize-ii))):Next
EndMacro
Procedure.w SwapWord(iValue.w)
Protected iReturn.w
SwapDataType(iValue,iReturn)
ProcedureReturn iReturn
EndProcedure
Procedure.l SwapLong(iValue.l)
Protected iReturn.l
SwapDataType(iValue,iReturn)
ProcedureReturn iReturn
EndProcedure
;}
; Windows Only
; Procedure.l SwapLong(Value.l)
; !MOV Eax, [p.v_Value]
; !BSWAP Eax
; ProcedureReturn
; EndProcedure
Procedure.i GetBitrate(ID.i, Layer.i, Value.i)
If ID = #MPEG1 ;{ MPEG 1
Select Value
Case 1
ProcedureReturn 32
Case 2
Select Layer
Case #Layer1
ProcedureReturn 64
Case #Layer2
ProcedureReturn 48
Case #Layer3
ProcedureReturn 40
EndSelect
Case 3
Select Layer
Case #Layer1
ProcedureReturn 96
Case #Layer2
ProcedureReturn 56
Case #Layer3
ProcedureReturn 48
EndSelect
Case 4
Select Layer
Case #Layer1
ProcedureReturn 128
Case #Layer2
ProcedureReturn 64
Case #Layer3
ProcedureReturn 56
EndSelect
Case 5
Select Layer
Case #Layer1
ProcedureReturn 160
Case #Layer2
ProcedureReturn 80
Case #Layer3
ProcedureReturn 64
EndSelect
Case 6
Select Layer
Case #Layer1
ProcedureReturn 192
Case #Layer2
ProcedureReturn 96
Case #Layer3
ProcedureReturn 80
EndSelect
Case 7
Select Layer
Case #Layer1
ProcedureReturn 224
Case #Layer2
ProcedureReturn 112
Case #Layer3
ProcedureReturn 96
EndSelect
Case 8
Select Layer
Case #Layer1
ProcedureReturn 256
Case #Layer2
ProcedureReturn 128
Case #Layer3
ProcedureReturn 112
EndSelect
Case 9
Select Layer
Case #Layer1
ProcedureReturn 288
Case #Layer2
ProcedureReturn 160
Case #Layer3
ProcedureReturn 128
EndSelect
Case 10
Select Layer
Case #Layer1
ProcedureReturn 320
Case #Layer2
ProcedureReturn 192
Case #Layer3
ProcedureReturn 160
EndSelect
Case 11
Select Layer
Case #Layer1
ProcedureReturn 352
Case #Layer2
ProcedureReturn 224
Case #Layer3
ProcedureReturn 192
EndSelect
Case 12
Select Layer
Case #Layer1
ProcedureReturn 384
Case #Layer2
ProcedureReturn 256
Case #Layer3
ProcedureReturn 224
EndSelect
Case 13
Select Layer
Case #Layer1
ProcedureReturn 416
Case #Layer2
ProcedureReturn 320
Case #Layer3
ProcedureReturn 256
EndSelect
Case 14
Select Layer
Case #Layer1
ProcedureReturn 448
Case #Layer2
ProcedureReturn 384
Case #Layer3
ProcedureReturn 320
EndSelect
EndSelect
;}
Else
Select Value
Case 1
If Layer = #Layer1
ProcedureReturn 32
Else
ProcedureReturn 8
EndIf
Case 2
If Layer = #Layer1
ProcedureReturn 48
Else
ProcedureReturn 16
EndIf
Case 3
If Layer = #Layer1
ProcedureReturn 56
Else
ProcedureReturn 24
EndIf
Case 4
If Layer = #Layer1
ProcedureReturn 64
Else
ProcedureReturn 32
EndIf
Case 5
If Layer = #Layer1
ProcedureReturn 80
Else
ProcedureReturn 40
EndIf
Case 6
If Layer = #Layer1
ProcedureReturn 96
Else
ProcedureReturn 48
EndIf
Case 7
If Layer = #Layer1
ProcedureReturn 112
Else
ProcedureReturn 56
EndIf
Case 8
If Layer = #Layer1
ProcedureReturn 128
Else
ProcedureReturn 64
EndIf
Case 9
If Layer = #Layer1
ProcedureReturn 144
Else
ProcedureReturn 80
EndIf
Case 10
If Layer = #Layer1
ProcedureReturn 160
Else
ProcedureReturn 96
EndIf
Case 11
If Layer = #Layer1
ProcedureReturn 176
Else
ProcedureReturn 112
EndIf
Case 12
If Layer = #Layer1
ProcedureReturn 192
Else
ProcedureReturn
EndIf
Case 13
If Layer = #Layer1
ProcedureReturn 224
Else
ProcedureReturn 144
EndIf
Case 14
If Layer = #Layer1
ProcedureReturn 256
Else
ProcedureReturn 160
EndIf
EndSelect
EndIf
EndProcedure
Procedure.i GetSamplingRate(ID.i, Value.i)
Select ID
Case #MPEG1
Select Value
Case 0
ProcedureReturn 44100
Case 1
ProcedureReturn 48000
Case 2
ProcedureReturn 32000
EndSelect
Case #MPEG2
Select Value
Case 0
ProcedureReturn 22050
Case 1
ProcedureReturn 24000
Case 2
ProcedureReturn 16000
EndSelect
Case #MPEG25
Select Value
Case 0
ProcedureReturn 11025
Case 1
ProcedureReturn 12000
Case 2
ProcedureReturn 8000
EndSelect
EndSelect
EndProcedure
Procedure.i LoadBinaryFile(FileName.s)
Protected ID, Size.q, Bytes.q, *Memory = #Null
ID = ReadFile(#PB_Any, FileName, #PB_File_SharedRead)
If ID
Size = Lof(ID)
If Size
*Memory = AllocateMemory(Size)
If *Memory
Bytes = ReadData(ID, *Memory, Size)
If Bytes = 0 Or Bytes <> Size
FreeMemory(*Memory)
*Memory = #Null
EndIf
EndIf
CloseFile(ID)
EndIf
EndIf
ProcedureReturn *Memory
EndProcedure
Procedure.i GetMP3Info(FileName.s, *mp3_data.mp3_data)
Protected *mp3
Protected *ID3V1_Frame.ID3TAGV1
Protected *ID3V2_Header.ID3TAGV2_HEADER
Protected *ID3V2_Frame.ID3TAGV2_FRAME
Protected *mp3_start
Protected *mp3_stop
Protected *Memory
Protected *EndMem
Protected Bytes.w, Byte.b, Size, i, PosNow, EndPosition
Protected FrameID.s, FrameText.s, FrameSize, Encoding, Add
Protected PaddingBytes
*mp3 = LoadBinaryFile(FileName)
If Not *mp3 : ProcedureReturn #False : EndIf
If Not *mp3_data : ProcedureReturn #False : EndIf
If Genre(0) = ""
Restore GENRES
For i = 0 To 125
Read.s Genre(i)
Next i
EndIf
ClearStructure(*mp3_data, mp3_data)
NewMap *mp3_data\ID3Tag()
; Get ID3V2 Tag if present
*ID3V2_Header = *mp3
If PeekS(*ID3V2_Header + OffsetOf(ID3TAGV2_HEADER\Identifier), SizeOf(ID3TAGV2_HEADER\Identifier), #PB_Ascii) = "ID3" And *ID3V2_Header\Version[0] = 3
Size = 0
For i = 3 To 0 Step - 1
Size + ((*ID3V2_Header\Size[3 - i] & $7F) << (7 * i))
Next i
PosNow = SizeOf(ID3TAGV2_HEADER)
*mp3_data\ID3v2 = #True
EndIf
Repeat
*ID3V2_Frame = *mp3 + PosNow
FrameID = PeekS(*ID3V2_Frame + OffsetOf(ID3TAGV2_FRAME\FrameID), SizeOf(ID3TAGV2_FRAME\FrameID), #PB_Ascii)
If FrameID = "" : Break : EndIf
FrameSize = *ID3V2_Frame\Size & $FFFFFFFF
FrameSize = SwapLong(FrameSize)
EndPosition + FrameSize
Encoding = #PB_Ascii
Add = 1
If *ID3V2_Frame\Encoding = 1
Encoding = #PB_UTF16
Add = 2
EndIf
FrameSize - 1
If Left(LCase(FrameID), 1) = "t"
If PeekW(*ID3V2_Frame + OffsetOf(ID3TAGV2_FRAME\Data)) & $FFFF = $FFFE Or PeekW(*ID3V2_Frame + OffsetOf(ID3TAGV2_FRAME\Data)) & $FFFF = $FEFF
FrameText = PeekS(*ID3V2_Frame + OffsetOf(ID3TAGV2_FRAME\Data) + 2, (FrameSize / Add) - 1, #PB_UTF16)
Else
FrameText = PeekS(*ID3V2_Frame + OffsetOf(ID3TAGV2_FRAME\Data), FrameSize / Add, Encoding)
EndIf
ElseIf LCase(FrameID) = "comm"
If PeekW(*ID3V2_Frame + 3 + OffsetOf(ID3TAGV2_FRAME\Data)) & $FFFF = $FFFE Or PeekW(*ID3V2_Frame + 3 + OffsetOf(ID3TAGV2_FRAME\Data)) & $FFFF = $FEFF
FrameText = PeekS(*ID3V2_Frame + OffsetOf(ID3TAGV2_FRAME\Data) + 5, (FrameSize / Add) - 4, #PB_UTF16)
Else
FrameText = PeekS(*ID3V2_Frame + 3 + OffsetOf(ID3TAGV2_FRAME\Data), FrameSize / Add, Encoding)
EndIf
EndIf
*mp3_data\ID3Tag(UCase(FrameID)) = FrameText
PosNow + FrameSize + OffsetOf(ID3TAGV2_FRAME\Data)
Until EndPosition >= Size
*mp3_start = *mp3 + Size
; Get ID3V1 Tag if present
*ID3V1_Frame = *mp3 + MemorySize(*mp3) - 128
If PeekS(*ID3V1_Frame + OffsetOf(ID3TAGV1\TAG), 3, #PB_Ascii) = "TAG"
*mp3_stop = *ID3V1_Frame - 1
If Not FindMapElement(*mp3_data\ID3Tag(), "TIT2")
*mp3_data\ID3Tag("TIT2") = PeekS(*ID3V1_Frame + OffsetOf(ID3TAGV1\Title), SizeOf(ID3TAGV1\Title), #PB_Ascii)
EndIf
If Not FindMapElement(*mp3_data\ID3Tag(), "TPE1")
*mp3_data\ID3Tag("TPE1") = PeekS(*ID3V1_Frame + OffsetOf(ID3TAGV1\Artist), SizeOf(ID3TAGV1\Artist), #PB_Ascii)
EndIf
If Not FindMapElement(*mp3_data\ID3Tag(), "TYER")
*mp3_data\ID3Tag("TYER") = PeekS(*ID3V1_Frame + OffsetOf(ID3TAGV1\Year), SizeOf(ID3TAGV1\Year), #PB_Ascii)
EndIf
If Not FindMapElement(*mp3_data\ID3Tag(), "GENRE")
If *ID3V1_Frame\Genre >= 0 And *ID3V1_Frame\Genre <=125
*mp3_data\ID3Tag("TCON") = Genre(*ID3V1_Frame\Genre)
EndIf
EndIf
If *ID3V1_Frame\Comment[28] = 0
*mp3_data\ID3v11 = #True
If Not FindMapElement(*mp3_data\ID3Tag(), "TRCK")
*mp3_data\ID3Tag("TRCK") = Str(*ID3V1_Frame\Comment[29])
EndIf
If Not FindMapElement(*mp3_data\ID3Tag(), "COMM")
*mp3_data\ID3Tag("COMM") = PeekS(*ID3V1_Frame + OffsetOf(ID3TAGV1\Comment), 28, #PB_Ascii)
EndIf
Else
*mp3_data\ID3v10 = #True
If Not FindMapElement(*mp3_data\ID3Tag(), "TRCK")
*mp3_data\ID3Tag("TRCK") = Str(0)
EndIf
If Not FindMapElement(*mp3_data\ID3Tag(), "COMM")
*mp3_data\ID3Tag("COMM") = PeekS(*ID3V1_Frame + OffsetOf(ID3TAGV1\Comment), SizeOf(ID3TAGV1\Comment), #PB_Ascii)
EndIf
EndIf
Else
*mp3_stop = *mp3 + MemorySize(*mp3)
EndIf
*Memory = *mp3_start
*EndMem = *mp3_stop
Repeat
Bytes = uint16(PeekW(*Memory))
If (Bytes >> 5) & %11111111111 = 2047 : Break : EndIf ; Sync
*Memory + 1
Until *Memory >= *EndMem
If (Bytes >> 5) & %11111111111 = 2047
*mp3_data\ID = (Bytes >> 3) & %11
*mp3_data\Layer = (Bytes >> 1) & %11
*mp3_data\Protection = Bytes & %1
Byte = uint8(PeekB(*Memory + 2))
*mp3_data\Bitrate = GetBitrate(*mp3_data\ID, *mp3_data\Layer, (Byte >> 4) & %1111)
*mp3_data\Samplingrate = GetSamplingRate(*mp3_data\ID, (Byte >> 2) & %11)
*mp3_data\Padding = (Byte >> 1) & %1
*mp3_data\Private = Byte & %1
Byte = uint8(PeekB(*Memory + 3))
*mp3_data\Channel = (Byte >> 6) & %11
*mp3_data\Extension = (Byte >> 4) & %11
*mp3_data\Copyright = (Byte >> 3) & %1
*mp3_data\Original = (Byte >> 2) & %1
*mp3_data\Emphasis = Byte & %11
EndIf
If *mp3_data\Protection
*Memory + 2
EndIf
If *mp3_data\Padding
If *mp3_data\Layer = #Layer1
PaddingBytes = 4
Else
PaddingBytes = 1
EndIf
Else
PaddingBytes = 0
EndIf
*mp3_data\FrameLength = (144 * (*mp3_data\Bitrate * 1000) / *mp3_data\Samplingrate) + PaddingBytes
*mp3_data\Frames = (*mp3_stop - *mp3_start) / *mp3_data\FrameLength
*mp3_data\Duration = (*mp3_data\Frames * SamplesPerFrame(*mp3_data\Layer, *mp3_data\ID)) / 44100
FreeMemory(*mp3)
ProcedureReturn #True
EndProcedure
DataSection
GENRES:
Data.s "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop"
Data.s "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap"
Data.s "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks"
Data.s "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance"
Data.s "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise"
Data.s "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock"
Data.s "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream"
Data.s "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "PopFunk", "Jungle"
Data.s "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi"
Data.s "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock"
Data.s "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival"
Data.s "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock"
Data.s "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera"
Data.s "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam"
Data.s "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle"
Data.s "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall"
Data.s ""
EndDataSection
EndModule
UseModule MP3Info
Procedure.s FormatSeconds(lSeconds.q, Days = 0) ; by Hroudtwolf
Protected lHours.q, lMinutes, lDays.q, String.s, First.s
lMinutes = lSeconds / 60
lSeconds = lSeconds % 60
lHours = lMinutes / 60
lMinutes = lMinutes % 60
If Days
If lHours =>24
lDays = lHours / 24
lHours = lHours % 24
EndIf
First.s = Str(lDays) + ":" + RSet (Str(lHours) , 2 , "0") + ":"
Else
If lHours > 0
First.s = Str(lHours) + ":"
Else
First.s = ""
EndIf
EndIf
String.s = First + RSet (Str(lMinutes) , 2 , "0") + ":" + RSet (Str(lSeconds) , 2 , "0")
ProcedureReturn String
EndProcedure
Define m.mp3_data
GetMP3Info("Your MP3 File.mp3", @m)
Debug FormatSeconds(m\Duration)
ForEach m\ID3Tag()
Debug MapKey(m\ID3Tag()) + ": " + m\ID3Tag()
Next