﻿#include-once
#include <FileConstants.au3>
;===============================================================================
; Function Name:   _ReadID3Tag($sPath)
; Description::    gibt ein Array mit den Daten aus den ID3-Tags zurück
;                  unterstützt werden die ID3-Tag-Versionen 1.0, 1.1, 2.3 und 2.4
;                  bei v2.4 müssen sich die ID3-Tags am Anfang der Datei befinden
; Parameter(s):    $sPath = Pfad zu einer MP3-Datei
; Requirement(s):  min. AutoIt v3.3.0.0
; Return Value(s): bei Erfolg: Array mit den ID3-Tagdaten (@error = 0)
;                  im Fehlerfall bekommt @error:
;                  1 = Datei existiert nicht
;                  2 = Datei konnte nicht zum lesen geöffnet werden
;                  3 = falsche ID3 v2 Version
;                  4 = Datei ist keine MP3-Datei
; Author(s):       Oscar (www.autoit.de)
;===============================================================================
Func _ReadID3Tag($sPath)
	If Not FileExists($sPath) Then Return SetError(1, 0, 0)
	Local $hFile, $sData, $sID3Header, $iID3HeaderSize = 0, $iOffset, $iSize, $tmp
	Local $aID3v2Tags[8] = ['TIT2', 'TPE1', 'TALB', 'TYER', 'TLEN', 'TRCK', 'TCON', 'TENC']
	Local $aID3[11][2] = [ _
			['Title', ''],['Artist', ''],['Album', ''],['Year', ''], _
			['Length', '0'],['Track', ''],['Genre', ''],['Encoder', ''], _
			['MPEG-Version', ''],['Bitrate', ''],['Sample-Freq.', '']]
	Local $aMP3Version[4] = ['MPEG2.5', 'Reserved', 'MPEG2', 'MPEG1']
	Local $aMP3Layer[4] = ['Reserved', 'Layer III', 'Layer II', 'Layer I']
	Local $aMP3Bitrate[5][16] = [ _
			[000, 032, 064, 096, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 000], _
			[000, 032, 048, 056, 064, 080, 096, 112, 128, 160, 192, 224, 256, 320, 384, 000], _
			[000, 032, 040, 048, 056, 064, 080, 096, 112, 128, 160, 192, 224, 256, 320, 000], _
			[000, 032, 048, 056, 064, 080, 096, 112, 128, 144, 160, 176, 192, 224, 256, 000], _
			[000, 008, 016, 024, 032, 040, 048, 056, 064, 080, 096, 112, 128, 144, 160, 000]]
	Local $aSampleFreq[3][4] = [[44100, 48000, 32000, 0],[22050, 24000, 16000, 0],[11025, 12000, 8000, 0]]
	Local $sMP3FrameHeader, $iMP3Version, $sMP3Version, $sMP3Layer, $iMP3Bitrate, $iMP3SampleFreq
	Local $iVBRFrames = -1, $iVBRFilesize, $iVBRFlags
	$hFile = FileOpen($sPath, 16)
	If $hFile = -1 Then Return SetError(2, 0, 0)
	$sData = Binary(FileRead($hFile, 4))
	If BinaryMid($sData, 1, 3) = '0x494433' Then ; ID3 v2.x Kennung gefunden
		If (BinaryMid($sData, 4, 1) = '0x03') Or (BinaryMid($sData, 4, 1) = '0x04') Then ; nur v2.3 und 2.4
			FileSetPos($hFile, 2, $FILE_CURRENT) ; 2 Bytes überspringen
			For $i = 0 To 3 ; berechne ID3-Headergröße (4 Bytes, jedoch nur jeweils die unteren 7 Bit)
				$iID3HeaderSize = BitShift($iID3HeaderSize, -7) + BitAND(Binary(FileRead($hFile, 1)), 0x7F)
			Next
			If $iID3HeaderSize > 0 Then
				$sID3Header = Binary(FileRead($hFile, $iID3HeaderSize)) ; lese gesamten ID3-Header
				For $i = 0 To 7
					$iOffset = StringInStr(BinaryToString($sID3Header), $aID3v2Tags[$i], 2) ; Offset zu dem ID3-Tag
					If $iOffset > 0 Then
						$iSize = Hex(BinaryMid($sID3Header, $iOffset + 4, 4)) ; Größe des ID3-Frames
						$tmp = BinaryMid($sID3Header, $iOffset + 11, Dec($iSize) - 1)
						If BinaryMid($tmp, 1, 2) = '0xFFFE' Then
							For $x = 3 To BinaryLen($tmp) Step 2
								$aID3[$i][1] &= BinaryToString(BinaryMid($tmp, $x, 2), 2)
							Next
						Else
							For $x = 1 To BinaryLen($tmp)
								$aID3[$i][1] &= BinaryToString(BinaryMid($tmp, $x, 1))
							Next
						EndIf
						$aID3[$i][1] = StringReplace($aID3[$i][1], Chr(0), '')
					EndIf
				Next
			EndIf
			Do
				$sData = String(FileRead($hFile, 1))
				If @error Then ExitLoop
				If BitAND($sData, 0xff) = 0xff Then
					FileSetPos($hFile, -1, $FILE_CURRENT)
					$sData = String(FileRead($hFile, 4))
				EndIf
			Until BitAND($sData, 0xFFE00000) = 0xFFE00000
		Else
			FileClose($hFile)
			Return SetError(3, 0, 0)
		EndIf
	Else ; ID3 v1.x
		$iOffset = FileGetPos($hFile)
		FileSetPos($hFile, -128, $FILE_END)
		$sID3Header = BinaryToString(FileRead($hFile, 3))
		If $sID3Header = 'TAG' Then
			$aID3[0][1] = StringReplace(BinaryToString(FileRead($hFile, 30)), Chr(0), '')
			$aID3[1][1] = StringReplace(BinaryToString(FileRead($hFile, 30)), Chr(0), '')
			$aID3[2][1] = StringReplace(BinaryToString(FileRead($hFile, 30)), Chr(0), '')
			$aID3[3][1] = StringReplace(BinaryToString(FileRead($hFile, 4)), Chr(0), '')
		EndIf
		FileSetPos($hFile, $iOffset, 0)
	EndIf
	$sMP3FrameHeader = String($sData)
	If BitAND($sMP3FrameHeader, 0xFFE00000) <> 0xFFE00000 Then
		FileClose($hFile)
		Return SetError(4, 0, 0) ; keine MP3-Datei, dann Return
	EndIf
	$iMP3Version = BitShift(BitXOR($sMP3FrameHeader, 0xFFE00000), 19) ; welche MP3-Version
	$sMP3Version = $aMP3Version[$iMP3Version] ; in Textform
	$sMP3Layer = $aMP3Layer[BitShift(BitAND($sMP3FrameHeader, 0x60000), 17)] ; welcher Layer
	$aID3[8][1] = $sMP3Version & @CRLF & $sMP3Layer ; ins Ausgabe-Array
	$iMP3Bitrate = BitShift(BitAND($sMP3FrameHeader, 0xF000), 12) ; Bitraten-Index auslesen
	Switch $sMP3Version ; je nach MPEG-Version Bitrate aus der Tabelle holen
		Case 'MPEG1'
			$aID3[9][1] = $aMP3Bitrate[$iMP3Version - ($iMP3Version > 1)][$iMP3Bitrate]
		Case 'MPEG2', 'MPEG2.5'
			If $sMP3Layer = 'Layer I' Then
				$aID3[9][1] = $aMP3Bitrate[3][$iMP3Bitrate]
			Else
				$aID3[9][1] = $aMP3Bitrate[4][$iMP3Bitrate]
			EndIf
	EndSwitch
	$iMP3SampleFreq = BitShift(BitAND($sMP3FrameHeader, 0xC00), 10) ; Sample-Frequenz-Index auslesen
	$aID3[10][1] = $aSampleFreq[2 - ($iMP3Version - ($iMP3Version > 1))][$iMP3SampleFreq] ; und Wert aus der Tabelle holen
	Do ; evtl. Leerbytes überspringen
		$tmp = FileRead($hFile, 1)
		If @error Then ExitLoop
	Until $tmp <> 0x00 Or @error
	If $tmp = 0x58 And BinaryToString(FileRead($hFile, 3)) = 'ing' Then ; MP3 mit VBR (Xing-Header gefunden)?
		$iVBRFlags = '0x' & Hex(FileRead($hFile, 4)) ; VBR-Flags auslesen
		If BitAND($iVBRFlags, 0x3) Then ; wenn die Einträge vorhanden sind, dann...
			$iVBRFrames = Dec(Hex(FileRead($hFile, 4))) ; Anzahl der VBR-Frames auslesen
			$iVBRFilesize = Dec(Hex(FileRead($hFile, 4))) ; Dateigröße auslesen
			$aID3[4][1] = $iVBRFrames * 1152 / $aID3[10][1] * 1000 ; VBR Laufzeit
			$aID3[9][1] = 'VBR~' & Int($iVBRFilesize * 8 / ($aID3[4][1] / 1000) / 1000) & @CRLF ; VBR durchschnittliche Bitrate
		EndIf
	Else
		If $aID3[4][1] = 0 Then $aID3[4][1] = (FileGetSize($sPath) * 8) / ($aID3[9][1] * 1000) * 1000 ; alternative CBR Laufzeit
		$aID3[9][1] = 'CBR ' & $aID3[9][1] & @CRLF
	EndIf
	$aID3[4][1] = _MyTicksToTime($aID3[4][1]) ; Laufzeit (Ticks to hour:min:sec)
	$aID3[9][1] &= 'kBit/s'
	$aID3[10][1] &= ' Hz'
	FileClose($hFile)
	Return $aID3
EndFunc   ;==>_ReadID3Tag

Func _MyTicksToTime($iTicks)
	Local $iHour, $iMins, $iSecs
	$iHour = Int($iTicks / 3600000)
	$iTicks = Mod($iTicks, 3600000)
	$iMins = Int($iTicks / 60000)
	$iTicks = Mod($iTicks, 60000)
	$iSecs = Int($iTicks / 1000)
	Return StringFormat('%02i:%02i:%02i', $iHour, $iMins, $iSecs)
EndFunc   ;==>_MyTicksToTime
