#include <ButtonConstants.au3>
#include <Date.au3>
#include <GUIConstantsEx.au3>
#include <GuiListView.au3>
#include <GuiStatusBar.au3>
#include <ListViewConstants.au3>
#include <StaticConstants.au3>
#include <StructureConstants.au3>
#include <WindowsConstants.au3>

Global $sHeader = 'Title|Artist|Length|Album|Year|MPEG-Version|Bitrate|Sample-Freq.|Path' ; Die Überschriften für das Listview
Global $sInifile = @ScriptDir & '\MP3-Search.ini' ; Inidatei

Global $iWidth = IniRead($sInifile, 'Config', 'Width', 770)
Global $iHeight = IniRead($sInifile, 'Config', 'Height', 406)
Global $iLeft = IniRead($sInifile, 'Config', 'Left', @DesktopWidth / 2 - 385)
Global $iTop = IniRead($sInifile, 'Config', 'Top', @DesktopHeight / 2 - 203)

#region Hauptfenster
Global $sStyle = BitOR($GUI_SS_DEFAULT_GUI, $WS_MAXIMIZEBOX, $WS_SIZEBOX)
Global $hGui = GUICreate('MP3-Search', 770, 406, $iLeft, $iTop, $sStyle) ; Hauptfenster erstellen
GUISetBkColor(0xAAAAAA)

Global $hListView = GUICtrlCreateListView($sHeader, 5, 5, 760, 280, $LVS_SHOWSELALWAYS, BitOR($LVS_EX_FULLROWSELECT, $LVS_EX_DOUBLEBUFFER, $WS_EX_WINDOWEDGE)) ; Listview erstellen
GUICtrlSetResizing(-1, 2 + 4 + 32 + 64)
GUICtrlSetBkColor(-1, 0xaabbff)
Global $hLVHandle = GUICtrlGetHandle($hListView) ; das Handle vom Listview wird für die UDF-Listview-Funktionen benötigt
_GUICtrlListView_SetColumnWidth($hLVHandle, 0, 180)
_GUICtrlListView_SetColumnWidth($hLVHandle, 1, 180)
_GUICtrlListView_SetColumnWidth($hLVHandle, 2, 60)
_GUICtrlListView_SetColumnWidth($hLVHandle, 3, 120)
_GUICtrlListView_SetColumnWidth($hLVHandle, 4, 60)
_GUICtrlListView_SetColumnWidth($hLVHandle, 5, 100)
_GUICtrlListView_SetColumnWidth($hLVHandle, 6, 100)
_GUICtrlListView_SetColumnWidth($hLVHandle, 7, 90)
_GUICtrlListView_SetColumnWidth($hLVHandle, 8, 800)

GUICtrlCreateLabel('Suchbegriff:', 10, 302, 70, 22, $SS_RIGHT)
GUICtrlSetFont(-1, 10)
GUICtrlSetResizing(-1, 512 + 64)
Global $hInput = GUICtrlCreateInput('', 90, 300, 300, 22)
GUICtrlSetFont(-1, 10)
GUICtrlSetResizing(-1, 512 + 64)
Global $hInputOk = GUICtrlCreateButton('Suche starten', 400, 300, 100, 22)
GUICtrlSetFont(-1, 10)
GUICtrlSetResizing(-1, 512 + 64)

Global $hSearch = GUICtrlCreateButton('Alle Festplatten durchsuchen', 10, 340, 120, 35, $BS_MULTILINE)
GUICtrlSetResizing(-1, 512 + 64)
Global $hSearchPath = GUICtrlCreateButton('Verzeichnis durchsuchen', 140, 340, 100, 35, $BS_MULTILINE)
GUICtrlSetResizing(-1, 512 + 64)
Global $hLoad = GUICtrlCreateButton('Laden', 250, 340, 70, 35)
GUICtrlSetResizing(-1, 512 + 64)
Global $hSave = GUICtrlCreateButton('Speichern', 330, 340, 70, 35)
GUICtrlSetResizing(-1, 512 + 64)
Global $hDel = GUICtrlCreateButton("Markierte Einträge löschen", 410, 340, 90, 35, $BS_MULTILINE)
GUICtrlSetResizing(-1, 512 + 64)
GUICtrlSetState(-1, $GUI_DISABLE)
Global $hDelAll = GUICtrlCreateButton('Alle Einträge löschen', 510, 340, 90, 35, $BS_MULTILINE)
GUICtrlSetResizing(-1, 512 + 64)

Global $aParts[3] = [300, 360, -1], $aText[3] = ['Fertig.', '0', '']
Global $hStatus = _GUICtrlStatusBar_Create($hGui, $aParts, $aText)
#endregion Hauptfenster

GUISetState(@SW_SHOW, $hGui) ; Hauptfenster sichtbar machen

Global $fStop = False, $iSearchIndex = -1, $iSelCount, $iSelOld
HotKeySet('{ESC}', '_Stop')

Dim $aAccelKeys[1][2] = [['{DEL}', $hDel]] ; beim drücken der [Delete]-Taste wird "Case $hDel" ausgeführt
GUISetAccelerators($aAccelKeys, $hGui)

GUIRegisterMsg($WM_SIZE, '_WM_SIZE')
GUIRegisterMsg($WM_MOVE, '_WM_MOVE')
GUIRegisterMsg($WM_GETMINMAXINFO, '_WM_GETMINMAXINFO')
GUIRegisterMsg($WM_NOTIFY, '_WM_NOTIFY')
GUIRegisterMsg($WM_COMMAND, '_WM_COMMAND')

WinMove($hGui, '', $iLeft, $iTop, $iWidth, $iHeight)

While 1
	Switch GUIGetMsg() ; anhand der Control-ID das entsprechende Case aufrufen
		Case $hSearch
			_GUICtrlListView_DeleteAllItems($hLVHandle) ; Alle Listview-Einträge löschen
			_GUICtrlStatusBar_SetText($hStatus, 0, 1)
			$iLVCount = 0 ; Zähler auf Null setzen
			$aDrives = DriveGetDrive('FIXED') ; Alle Festplatten-Buchstaben holen
			If Not @error Then
				GUISetCursor(15, 1, $hGui)
				For $i = 1 To $aDrives[0] ; Alle Festplatten durchgehen
					_GUICtrlStatusBar_SetText($hStatus, 'Durchsuche Festplatte  "' & StringUpper($aDrives[$i]) & '\"', 0)
					_DriveSearch(StringUpper($aDrives[$i]))
				Next
				GUISetCursor(2, 1, $hGui)
			EndIf
			_GUICtrlStatusBar_SetText($hStatus, _GUICtrlListView_GetItemCount($hLVHandle), 1)
			_GUICtrlStatusBar_SetText($hStatus, '', 2)
			If $fStop Then
				$fStop = False
				_GUICtrlStatusBar_SetText($hStatus, 'Abgebrochen!', 0)
			Else
				_GUICtrlStatusBar_SetText($hStatus, 'Fertig.', 0)
			EndIf
		Case $hSearchPath
			$sPath = FileSelectFolder('Verzeichnis auswählen!', '', 2, '', $hGui)
			If FileExists($sPath) Then
				_GUICtrlListView_DeleteAllItems($hLVHandle) ; Alle Listview-Einträge löschen
				$iLVCount = 0 ; Zähler auf Null setzen
				GUISetCursor(15, 1, $hGui)
				_GUICtrlStatusBar_SetText($hStatus, 'Durchsuche Verzeichnis  "' & $sPath & '"', 0)
				_DriveSearch($sPath)
				GUISetCursor(2, 1, $hGui)
				_GUICtrlStatusBar_SetText($hStatus, _GUICtrlListView_GetItemCount($hLVHandle), 1)
				_GUICtrlStatusBar_SetText($hStatus, '', 2)
				If $fStop Then
					$fStop = False
					_GUICtrlStatusBar_SetText($hStatus, 'Abgebrochen!', 0)
				Else
					_GUICtrlStatusBar_SetText($hStatus, 'Fertig.', 0)
				EndIf
			EndIf
		Case $hInput, $hInputOk
			_GUICtrlStatusBar_SetText($hStatus, 'Suche in Liste...', 0)
			GUISetCursor(15, 1, $hGui)
			_LVSearch()
			GUISetCursor(2, 1, $hGui)
			_GUICtrlStatusBar_SetText($hStatus, 'Fertig.', 0)
		Case $hListView ; User hat auf eine Spaltenüberschrift geklickt (sortieren)
			If _GUICtrlListView_GetItemCount($hLVHandle) > 0 Then
				Dim $aDescending[_GUICtrlListView_GetColumnCount($hLVHandle)]
				GUISetCursor(15, 1, $hGui)
				$iTimer = TimerInit()
				_GUICtrlStatusBar_SetText($hStatus, 'Sortiere Daten! Bitte warten...', 0)
				_GUICtrlListView_SimpleSort($hLVHandle, $aDescending, GUICtrlGetState($hListView))
				_GUICtrlStatusBar_SetText($hStatus, _TicksToTimeFormat(TimerDiff($iTimer), '%mm:%ss.%ms'), 2)
				_GUICtrlStatusBar_SetText($hStatus, 'Fertig.', 0)
				GUISetCursor(2, 1, $hGui)
			EndIf
		Case $hLoad ; User hat auf 'Laden' geklickt
			GUISetCursor(15, 1, $hGui)
			_GUICtrlStatusBar_SetText($hStatus, 'Lade Daten...', 0)
			Load()
			_GUICtrlStatusBar_SetText($hStatus, 'Fertig.', 0)
			GUISetCursor(2, 1, $hGui)
		Case $hSave ; User hat auf 'Speichern' geklickt
			GUISetCursor(15, 1, $hGui)
			_GUICtrlStatusBar_SetText($hStatus, 'Speichere Daten...', 0)
			Save()
			_GUICtrlStatusBar_SetText($hStatus, 'Fertig.', 0)
			_GUICtrlStatusBar_SetText($hStatus, '', 2)
			GUISetCursor(2, 1, $hGui)
		Case $hDel
			GUISetCursor(15, 1, $hGui)
			_GUICtrlListView_DeleteItemsSelected($hLVHandle)
			_GUICtrlStatusBar_SetText($hStatus, _GUICtrlListView_GetItemCount($hLVHandle), 1)
			GUISetCursor(2, 1, $hGui)
		Case $hDelAll ; User hat auf 'Alle Einträge löschen' geklickt
			If MsgBox(256 + 32 + 4, 'Einträge löschen', 'Wollen sie wirklich alle Einträge löschen?') = 6 Then
				_GUICtrlListView_DeleteAllItems($hLVHandle)
				_GUICtrlStatusBar_SetText($hStatus, 0, 1)
			EndIf
		Case $GUI_EVENT_CLOSE ; User hat auf das Schließen-Symbol geklickt
;~ 			If MsgBox(32 + 4, 'Save', 'Wollen sie vor beenden noch Speichern?') = 6 Then Save()
			Exit ; Programm beenden
	EndSwitch
	$iSelCount = _GUICtrlListView_GetSelectedCount($hLVHandle) ; Anzahl der markierten Items holen
	If $iSelCount <> $iSelOld Then ; vergleichen ob der Wert vom vorherigen abweicht (Flackern vermeiden)
		$iSelOld = $iSelCount ; wenn ja, dann alten gleich neuen Wert
		If $iSelCount > 0 Then ; wenn Einträge markiert sind
			GUICtrlSetState($hDel, $GUI_ENABLE) ; dann Button aktivieren
			GUICtrlSetData($hDel, StringFormat('(%i) Markierte Einträge löschen', $iSelCount)) ; und den Text auf dem Button anpassen
		Else ; keine Einträge markiert
			GUICtrlSetState($hDel, $GUI_DISABLE) ; dann Button deaktivieren
			GUICtrlSetData($hDel, 'Markierte Einträge löschen') ; und den Text auf dem Button anpassen
		EndIf
	EndIf
WEnd

Func _Stop()
	$fStop = True
EndFunc   ;==>_Stop

Func _DriveSearch($sPath)
	Local $aNewMP3, $aID3Tags, $sNewItem, $aItem
	If StringRight($sPath, 1) <> '\' Then $sPath &= '\'
	_GUICtrlStatusBar_SetText($hStatus, 'Bitte warten...', 2)
	$aNewMP3 = _RecursiveFileListToArray($sPath, '\.mp3', 1) ; Alle MP3s auf der Festplatte auslesen
	If Not @error And $aNewMP3[0] > 0 Then
		_GUICtrlListView_SetItemCount($hListView, _GUICtrlListView_GetItemCount($hLVHandle) + $aNewMP3[0])
		_GUICtrlStatusBar_SetText($hStatus, 'Lese ID3-Tags... (' & $aNewMP3[0] & ')', 0)
		For $j = 1 To $aNewMP3[0] ; die gefundenen MP3s durchgehen
			$aID3Tags = _ReadID3Tag($aNewMP3[$j]) ; die ID3-Tags auslesen
			If Not @error Then
				For $k = 0 To 10
					$aID3Tags[$k][1] = StringReplace($aID3Tags[$k][1], '|', '') ; evtl. vorhandene "|" entfernen
				Next
				_GUICtrlStatusBar_SetText($hStatus, _GUICtrlListView_GetItemCount($hLVHandle), 1)
				_GUICtrlStatusBar_SetText($hStatus, $aNewMP3[$j], 2)
				$sNewItem = $aID3Tags[0][1] & '|' & $aID3Tags[1][1] & '|' & $aID3Tags[4][1] & '|' & $aID3Tags[2][1] & '|' & $aID3Tags[3][1]
				$sNewItem &= '|' & $aID3Tags[8][1] & '|' & $aID3Tags[9][1] & '|' & $aID3Tags[10][1] & '|' & $aNewMP3[$j]
				GUICtrlCreateListViewItem($sNewItem, $hListView)
			EndIf
			If $fStop Then ExitLoop
		Next
	EndIf
EndFunc   ;==>_DriveSearch

Func _WM_COMMAND($hWnd, $msg, $wParam, $lParam)
	Local $nNotifyCode = BitShift($wParam, 16)
	Local $nID = BitAND($wParam, 0xFFFF)
	If $nID = $hInput And $nNotifyCode = 256 Then
		GUICtrlSetBkColor($hInput, 0xffffff)
		$iSearchIndex = -1
		GUICtrlSetData($hInputOk, 'Suche starten')
		_GUICtrlListView_SetItemSelected($hLVHandle, -1, False)
	EndIf
	Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_COMMAND

Func _LVSearch()
	If _GUICtrlListView_GetItemCount($hLVHandle) = 0 Then Return
	Local $sSearch
	$sSearch = GUICtrlRead($hInput)
	$iSearchIndex = _GUICtrlListView_FindInText($hLVHandle, $sSearch, $iSearchIndex)
	If $iSearchIndex > -1 Then
		_GUICtrlListView_EnsureVisible($hLVHandle, $iSearchIndex)
		_GUICtrlListView_SetItemSelected($hLVHandle, $iSearchIndex)
		GUICtrlSetBkColor($hInput, 0xaaffaa)
		GUICtrlSetData($hInputOk, 'weitersuchen...')
	Else
		_GUICtrlListView_EnsureVisible($hLVHandle, 0)
		_GUICtrlListView_SetItemSelected($hLVHandle, -1, False)
		GUICtrlSetBkColor($hInput, 0xffaaaa)
	EndIf
EndFunc   ;==>_LVSearch

Func _WM_NOTIFY($hWnd, $msg, $wParam, $lParam)
	Local $hWndFrom, $iIDFrom, $iCode, $tNMHDR, $iSelected, $sExecutePath
	$tNMHDR = DllStructCreate($tagNMHDR, $lParam)
	$hWndFrom = HWnd(DllStructGetData($tNMHDR, 'hWndFrom'))
	$iCode = DllStructGetData($tNMHDR, 'Code')
	Switch $hWndFrom
		Case $hLVHandle
			Switch $iCode
				Case $NM_DBLCLK
					$iSelected = ControlListView($hGui, '', $hListView, 'GetSelected')
					$sExecutePath = _GUICtrlListView_GetItemText($hLVHandle, $iSelected, 8)
					ShellExecute($sExecutePath)
			EndSwitch
	EndSwitch
	Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_NOTIFY

Func _WM_SIZE($hWnd, $msg, $wParam, $lParam)
	Local $aGuiPos = WinGetPos($hGui)
	If Not BitAND(WinGetState($hGui), 16) Then
		IniWrite($sInifile, 'Config', 'Width', $aGuiPos[2])
		IniWrite($sInifile, 'Config', 'Height', $aGuiPos[3])
	EndIf
	_GUICtrlStatusBar_Resize($hStatus)
	Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_SIZE

Func _WM_MOVE($hWnd, $msg, $wParam, $lParam)
	Local $aGuiPos = WinGetPos($hGui)
	If Not BitAND(WinGetState($hGui), 16) Then
		IniWrite($sInifile, 'Config', 'Left', $aGuiPos[0])
		IniWrite($sInifile, 'Config', 'Top', $aGuiPos[1])
	EndIf
	Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_MOVE

Func _WM_GETMINMAXINFO($hWnd, $msg, $wParam, $lParam)
	If $hWnd = $hGui Then
		Local $minmaxinfo = DllStructCreate("int;int;int;int;int;int;int;int;int;int", $lParam)
		DllStructSetData($minmaxinfo, 7, 786) ; min X
		DllStructSetData($minmaxinfo, 8, 444) ; min Y
;~         DllStructSetData($minmaxinfo, 9, 800) ; max X
;~         DllStructSetData($minmaxinfo, 10, 800) ; max Y
	EndIf
EndFunc   ;==>_WM_GETMINMAXINFO

Func Load() ; Datenbank-Datei laden
	Local $sDBFile, $hFile, $sContent, $aNewItems, $iTimer, $iCount
	FileChangeDir(@ScriptDir)
	$sDBFile = FileOpenDialog('MP3-Datenbank-Datei auswählen', @ScriptDir, 'MP3-Datenbank (*.db3)', 3, '', $hGui)
	If Not FileExists($sDBFile) Then Return
	$hFile = FileOpen($sDBFile, 0) ; Datei zum lesen öffnen
	If $hFile = -1 Then Return; wenn das öffnen nicht erfolgreich war, dann Funktion verlassen
	$sContent = FileRead($hFile) ; Datei komplett einlesen
	FileClose($hFile) ; Datei schließen
	$sContent = StringTrimRight($sContent, 2) ; das letzte @CRLF entfernen
	$aNewItems = StringSplit($sContent, @CRLF, 1) ; Den Dateiinhalt am Zeilenende splitten
	If Not IsArray($aNewItems) Then Return ; Wenn $aNewItems kein Array ist, dann Funktion verlassen
	_GUICtrlListView_DeleteAllItems($hLVHandle)
	_GUICtrlListView_SetItemCount($hLVHandle, $aNewItems[0])
	_GUICtrlStatusBar_SetText($hStatus, 'Lade Daten... (' & $aNewItems[0] & ')', 0)
	_GUICtrlListView_BeginUpdate($hLVHandle) ; Listview sperren
	$iTimer = TimerInit()
	For $i = 1 To $aNewItems[0] ; Alle Einträge des Arrays durchgehen
		$iCount += 1
		If TimerDiff($iTimer) > 100 Then
			$iTimer = TimerInit()
			_GUICtrlStatusBar_SetText($hStatus, $iCount, 1)
		EndIf
		GUICtrlCreateListViewItem($aNewItems[$i], $hListView) ; mit den eingelesenen Daten einen neuen Listview-Eintrag erstellen
	Next
	_GUICtrlStatusBar_SetText($hStatus, $iCount, 1)
	_GUICtrlListView_EndUpdate($hLVHandle) ; Listview wieder freigeben
EndFunc   ;==>Load

Func Save() ; Datenbank-Datei speichern
	Local $sDBFile, $hFile, $iCount, $iTimer
	$sDBFile = FileSaveDialog('MP3-Datenbank-Datei auswählen', '', 'MP3-Datenbank (*.db3)', 18, '', $hGui)
	If @error Then Return
	If StringRight($sDBFile, 4) <> '.db3' Then $sDBFile &= '.db3'
	$iCount = _GUICtrlListView_GetItemCount($hLVHandle); Anzahl der Listview-Einträge holen
	If $iCount = 0 Then Return ; Wenn das Listview keine Einträge enthält, dann Funktion verlassen
	$hFile = FileOpen($sDBFile, 2) ; Datei zum speichern (überschreiben) öffnen
	If $hFile = -1 Then Return; wenn das öffnen nicht erfolgreich war, dann Funktion verlassen
	$iTimer = TimerInit()
	For $i = 0 To $iCount - 1 ; Schleife, um alle Listview-Einträge durchzugehen
		FileWriteLine($hFile, _GUICtrlListView_GetItemTextString($hLVHandle, $i))
		If TimerDiff($iTimer) > 100 Then
			$iTimer = TimerInit()
			_GUICtrlStatusBar_SetText($hStatus, $i + 1, 2)
		EndIf
	Next
	FileClose($hFile) ; Datei schließen
EndFunc   ;==>Save

;===============================================================================
; Function Name:   _RecursiveFileListToArray($sPath[, $sPattern][, $iFlag][, $iFormat][, $iRecursion][, $sDelim])
; Description::    gibt Verzeichnisse (rekursiv) und/oder Dateien zurück, die einem RegExp-Pattern entsprechen
; Parameter(s):    $sPath = Startverzeichnis
;                  $sPattern = ein beliebiges RexExp-Pattern für die Auswahl
;                  $iFlag = Auswahl
;                           0 = Dateien & Verzeichnisse
;                           1 = nur Dateien
;                           2 = nur Verzeichnisse
;                  $iFormat = Rückgabeformat
;                             0 = String
;                             1 = Array mit [0] = Anzahl
;                             2 = Nullbasiertes Array
;                  $iRecursion = Verzeichnisse rekursiv durchsuchen
;                                0 = Nein
;                                1 = Ja
;                  $sDelim = Trennzeichen für die String-Rückgabe
; Requirement(s):  AutoIt 3.3.0.0
; Return Value(s): Array/String mit den gefundenen Dateien/Verzeichnissen
; Author(s):       Oscar (www.autoit.de)
;                  Anregungen von: bernd670 (www.autoit.de)
;===============================================================================
Func _RecursiveFileListToArray($sPath, $sPattern = '', $iFlag = 0, $iFormat = 1, $iRecursion = 1, $sDelim = @CRLF)
	Local $hSearch, $sFile, $sReturn = ''
	If StringRight($sPath, 1) <> '\' Then $sPath &= '\'
	$hSearch = FileFindFirstFile($sPath & '*.*')
	If @error Or $hSearch = -1 Then Return SetError(1, 0, $sReturn)
	While Not $fStop
		$sFile = FileFindNextFile($hSearch)
		If @error Then ExitLoop
		If @extended Then
			If StringRegExp($sPath & $sFile, $sPattern) And ($iFlag = 0 Or $iFlag = 2) Then $sReturn &= $sPath & $sFile & '\' & $sDelim
			If $iRecursion Then $sReturn &= _RecursiveFileListToArray($sPath & $sFile & '\', $sPattern, $iFlag, 0)
			ContinueLoop
		EndIf
		If StringRegExp($sFile, $sPattern) And ($iFlag = 0 Or $iFlag = 1) Then $sReturn &= $sPath & $sFile & $sDelim
	WEnd
	FileClose($hSearch)
	If $iFormat And $sReturn = '' Then Return StringSplit($sReturn, '', $iFormat)
	If $iFormat Then Return StringSplit(StringTrimRight($sReturn, StringLen($sDelim)), $sDelim, $iFormat)
	Return $sReturn
EndFunc   ;==>_RecursiveFileListToArray

;===============================================================================
; 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
			FileRead($hFile, 2) ; 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]) ; 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, 1)
					$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, 2)
		$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 & ' / ' & $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) ; 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]
	EndIf
	$aID3[4][1] = _TicksToTimeFormat($aID3[4][1]) ; Laufzeit (Ticks to hour:min:sec)
	$aID3[9][1] &= ' kBit/s'
	$aID3[10][1] &= ' Hz'
	FileClose($hFile)
	Return SetError(0, 0, $aID3)
EndFunc   ;==>_ReadID3Tag

;===============================================================================
; Function Name:   _TicksToTimeFormat($iTicks, $sFormat = '%hh:%mm:%ss')
; Description::    Diese Funktion wandelt Millisekunden in ein anzugebenes Zeitformat um
; Parameter(s):    $iTicks = Zeit in Millisekunden
;                  $sFormat:
;                            %hh für Stunden
;                            %mm für Minuten
;                            %ss für Sekunden
;                            %ms für Millisekunden
;                            sonstige Zeichen, die dazwischen stehen, werden übernommen
; Requirement(s):  -
; Return Value(s): Zeit im ausgewählten Format (String)
; Author(s):       Oscar (www.autoit.de)
;===============================================================================
Func _TicksToTimeFormat($iTicks, $sFormat = '%hh:%mm:%ss')
	Local $aTime[4] = [0, 0, 0, 0], $sOut, $aTimeFormat[4] = ['hh', 'mm', 'ss', 'ms'], $aFormat
	$aTime[2] = Int($iTicks / 1000)
	$aTime[3] = $iTicks - $aTime[2] * 1000
	$aTime[0] = Int($aTime[2] / 3600)
	$aTime[2] = Mod($aTime[2], 3600)
	$aTime[1] = Int($aTime[2] / 60)
	$aTime[2] = Mod($aTime[2], 60)
	$aFormat = StringRegExp($sFormat, '%([^%]+)', 3)
	If Not IsArray($aFormat) Then Return SetError(1, 0, $iTicks)
	For $i = 0 To UBound($aFormat) - 1
		For $j = 0 To UBound($aTimeFormat) - 1
			If StringLeft($aFormat[$i], 2) = $aTimeFormat[$j] Then $sOut &= StringFormat('%02i', $aTime[$j]) & StringMid($aFormat[$i], 3)
		Next
	Next
	Return $sOut
EndFunc   ;==>_TicksToTimeFormat