#include <ButtonConstants.au3>
#include <GUIConstantsEx.au3>
#include <GuiListView.au3>
#include <GuiStatusBar.au3>
#include <ListViewConstants.au3>

Global $sHeader = "Title|Artist|Album|Year|Length|MPEG-Version|Bitrate|Sample-Freq.|Path" ; Die Überschriften für das Listview
Global $sDBFile = @ScriptDir & "\MP3_Collection.txt" ; Pfad und Name der Datenbank-Datei

#Region Hauptfenster
Global $hGui = GUICreate("MP3-Search", 800, 500) ; Hauptfenster erstellen
Global $hListView = GUICtrlCreateListView($sHeader, 0, 0, 800, 420, $LVS_SHOWSELALWAYS) ; Listview erstellen

Global $hLVHandle = GUICtrlGetHandle($hListView) ; das Handle vom Listview wird für die UDF-Listview-Funktionen benötigt
_GUICtrlListView_SetColumn($hLVHandle, 0, "Title", 180, 0)
_GUICtrlListView_SetColumn($hLVHandle, 1, "Artist", 180, 0)
_GUICtrlListView_SetColumn($hLVHandle, 2, "Length", 60, 0)
_GUICtrlListView_SetColumn($hLVHandle, 3, "Album", 120, 0)
_GUICtrlListView_SetColumn($hLVHandle, 4, "Year", 60, 0)
_GUICtrlListView_SetColumn($hLVHandle, 5, "MPEG-Version", 100, 0)
_GUICtrlListView_SetColumn($hLVHandle, 6, "Bitrate", 100, 0)
_GUICtrlListView_SetColumn($hLVHandle, 7, "Sample-Freq.", 90, 0)
_GUICtrlListView_SetColumn($hLVHandle, 8, "Path", 800, 0)

Global $hSearch = GUICtrlCreateButton("Alle Festplatten durchsuchen", 10, 430, 160, 35, $BS_MULTILINE)
Global $hLoad = GUICtrlCreateButton("Laden", 230, 430, 70, 35)
Global $hSave = GUICtrlCreateButton("Speichern", 310, 430, 70, 35)
Global $hDelAll = GUICtrlCreateButton("Alle Einträge löschen", 500, 430, 90, 35, $BS_MULTILINE)

Global $aParts[3] = [160, 220, -1], $aText[3] =["Fertig.", "0", ""]
Global $hStatus = _GUICtrlStatusBar_Create($hGui, $aParts, $aText)
#EndRegion Hauptfenster

;~ _GUICtrlListView_RegisterSortCallBack($hLVHandle) ; damit man das Listview (mit Klick auf die Spaltenüberschrift) sortieren kann

GUISetState(@SW_SHOW, $hGui) ; Hauptfenster sichtbar machen

Global $fStop = False, $iCount = 0
HotKeySet("{ESC}", "_Stop")

While 1
	Switch GUIGetMsg() ; anhand der Control-ID das entsprechende Case aufrufen
		Case $hSearch
			_GUICtrlListView_DeleteAllItems($hLVHandle) ; Alle Listview-Einträge löschen
			$iCount = 0 ; Zähler auf Null setzen
			$aDrives = DriveGetDrive("FIXED") ; Alle Festplatten-Buchstaben holen
			If Not @error Then
				For $i = 1 To $aDrives[0] ; Alle Festplatten durchgehen
					$aDrives[$i] = StringUpper($aDrives[$i]) & "\"
					_GUICtrlStatusBar_SetText($hStatus, 'Durchsuche Festplatte  "' & $aDrives[$i] & '"', 0)
					_GUICtrlStatusBar_SetText($hStatus, "Bitte warten...", 2)
					$aNewMP3 = _RecursiveFileListToArray($aDrives[$i], '\.mp3', 1) ; Alle MP3s auf der Festplatte auslesen
					If Not @error Then
						_GUICtrlStatusBar_SetText($hStatus, "Lese ID3-Tags...", 0)
						_GUICtrlListView_SetItemCount($hListView, $iCount + $aNewMP3[0])
						For $j = 1 To $aNewMP3[0] ; die gefundenen MP3s durchgehen
							_GUICtrlStatusBar_SetText($hStatus, $iCount, 1)
							$aID3Tags = _ReadID3Tag($aNewMP3[$j]) ; die ID3-Tags auslesen
							If Not @error Then
								_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)
								$iCount += 1
							EndIf
							If $fStop Then ExitLoop 2 ; Wenn [ESC] gedrückt wurde, die Schleifen verlassen
						Next
					EndIf
				Next
			EndIf
			_GUICtrlStatusBar_SetText($hStatus, "", 2)
			If $fStop Then
				$fStop = False
				_GUICtrlStatusBar_SetText($hStatus, "Abgebrochen!", 0)
			Else
				_GUICtrlStatusBar_SetText($hStatus, "Fertig.", 0)
			EndIf
;~ 		Case $hListView ; User hat auf eine Spaltenüberschrift geklickt
;~ 			_GUICtrlListView_SortItems($hLVHandle, GUICtrlGetState($hListView)) ; Einträge entsprechend sortieren
		Case $hLoad ; User hat auf "Laden" geklickt
			_GUICtrlStatusBar_SetText($hStatus, "Lade Daten...", 0)
			Load()
			_GUICtrlStatusBar_SetText($hStatus, "Fertig.", 0)
		Case $hSave ; User hat auf "Speichern" geklickt
			_GUICtrlStatusBar_SetText($hStatus, "Speichere Daten...", 0)
			Save()
			_GUICtrlStatusBar_SetText($hStatus, "Fertig.", 0)
			_GUICtrlStatusBar_SetText($hStatus, "", 2)
		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)
				$iCount = 0
			EndIf
		Case $GUI_EVENT_CLOSE ; User hat auf das Schließen-Symbol geklickt (bzw. die ESC-Taste gedrückt)
			End()
;~ 			If MsgBox(32 + 4, "Beenden", "Wollen sie das Programm wirklich beenden?") = 6 Then End() ; wenn ja, dann End-Funktion aufrufen
	EndSwitch
WEnd

Func _Stop()
	$fStop = True
EndFunc

Func End()
;~ 	Save() ; wenn der vordere Kommentar entfernt wird, dann werden vor dem beenden noch die Daten gespeichert
	_GUICtrlListView_UnRegisterSortCallBack($hLVHandle) ; Sortierroutine wieder de-registrieren
	Exit ; Programm beenden
EndFunc

Func Load() ; Datenbank-Datei laden
	Local $hFile, $sContent, $aNewItems
	$hFile = FileOpen($sDBFile, 0) ; Datei zum lesen öffnen
	If $hFile <> -1 Then ; wenn das öffnen erfolgreich war, dann...
		$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_SetItemCount($hListView, $aNewItems[0])
		_GUICtrlListView_BeginUpdate($hLVHandle) ; Listview sperren
		For $i = 1 To $aNewItems[0] ; Alle Einträge des Arrays durchgehen
			$iCount += 1
			_GUICtrlStatusBar_SetText($hStatus, $iCount, 1)
			GUICtrlCreateListViewItem($aNewItems[$i], $hListView) ; mit den eingelesenen Daten einen neuen Listview-Eintrag erstellen
		Next
		_GUICtrlListView_EndUpdate($hLVHandle) ; Listview wieder freigeben
	EndIf
EndFunc   ;==>Load

Func Save() ; Datenbank-Datei speichern
	Local $sContent, $hFile, $iCount
	$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
	For $i = 0 To $iCount - 1 ; Schleife, um alle Listview-Einträge durchzugehen
		$sContent &= _GUICtrlListView_GetItemTextString($hLVHandle, $i) & @CRLF
		_GUICtrlStatusBar_SetText($hStatus, $i + 1, 2)
	Next
	$hFile = FileOpen($sDBFile, 2) ; Datei zum speichern (überschreiben) öffnen
	If $hFile <> -1 Then ; wenn das öffnen erfolgreich war, dann...
		FileWrite($hFile, $sContent)
		FileClose($hFile) ; Datei schließen
	EndIf
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 True
		$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

;===============================================================================
; 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] = _MyTicksToTime($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

Func _MyTicksToTime($iTicks)
	Local $iHour, $iMins, $iSecs
	$iHour = Int($iTicks / 3600000)
	$iTicks -= $iHour * 3600000
	$iMins = Int($iTicks / 60000)
	$iTicks -= $iMins * 60000
	$iSecs = Int($iTicks / 1000)
	Return StringFormat('%02i:%02i:%02i', $iHour, $iMins, $iSecs)
EndFunc   ;==>_MyTicksToTime
