;~ Author Ian Maxwell, llewxam @ AutoIt forum ;~ AutoIt Version 3.3.14.2 ;~ Date August - September 2015 ;~ Purpose Strip out all ID3V1 and ID3V2 tags ;~ Thanks Predrag Supurovic, for putting together the information used to decipher the header data at http://www.datavoyage.com/mpgscript/mpeghdr.htm #include #include #include #include #include #include #include Local $PossibleV1Locations[3] = [-128, -129, -256] Local $IndexArray[15][5] = [[0, 0, 0, 0, 0], [32000, 32000, 32000, 32000, 8000], [64000, 48000, 40000, 48000, 16000], [96000, 56000, 48000, 56000, 24000], [128000, 64000, 56000, 64000, 32000], [160000, 80000, 64000, 80000, 40000], [192000, 96000, 80000, 96000, 48000], [224000, 112000, 96000, 112000, 56000], [256000, 128000, 112000, 128000, 64000], [288000, 160000, 128000, 144000, 80000], [320000, 192000, 160000, 160000, 96000], [352000, 224000, 192000, 176000, 112000], [384000, 256000, 224000, 192000, 128000], [416000, 320000, 256000, 224000, 144000], [448000, 384000, 320000, 256000, 160000]] Local $IndexConvert[4] = [8, 4, 2, 1] Local $SampleArray[4][3] = [[44100, 22050, 11025], [48000, 24000, 12000], [32000, 16000, 8000], [0, 0, 0]] Local $SampleConvert[2] = [2, 1] Local $BitValue[8] = [1, 2, 4, 8, 16, 32, 64, 128] Local $ChunkSize = 2000, $FrameLimit = 100 Local $FileCount = 0, $Progress = 0, $Start, $RemovedID3V1, $RemovedID3V2, $nBytes Local $StatusBarSections[9] = [40, 95, 140, 240, 350, 450, 560, 660, 760] $GUI = GUICreate("ID3 Clean 1.24", 800, 552, Default, Default, -1, $WS_EX_ACCEPTFILES) GUISetBkColor(0xb2ccff, $GUI) $ShowWhere = GUICtrlCreateInput("", 10, 10, 560, 25) GUICtrlSetFont($ShowWhere, 13) GUICtrlSetState($ShowWhere, $GUI_DROPACCEPTED) $Where = GUICtrlCreateButton("Browse", 580, 10, 100, 25) $Go = GUICtrlCreateButton("GO", 690, 10, 100, 25) $FileList = _GUICtrlListView_Create($GUI, "", 10, 45, 780, 400) _GUICtrlListView_SetExtendedListViewStyle($FileList, BitOR($LVS_EX_GRIDLINES, $LVS_EX_FULLROWSELECT)) _GUICtrlListView_InsertColumn($FileList, 0, "MP3 File", 560) _GUICtrlListView_InsertColumn($FileList, 1, "ID3V1", 65) _GUICtrlListView_InsertColumn($FileList, 2, "ID3V2", 130) $TotslProgress = GUICtrlCreateProgress(10, 450, 780, 10) GUICtrlCreateGroup("Frame headers needed to identify stream (10 = fastest, least acurrate, 150 = slowest, most accurate)", 5, 470, 555, 50) $SetLimit10 = GUICtrlCreateRadio("10", 15, 490, 50, 20) $SetLimit50 = GUICtrlCreateRadio("50", 85, 490, 50, 20) $SetLimit150 = GUICtrlCreateRadio("150", 155, 490, 50, 20) GUICtrlCreateGroup("", -99, -99, 1, 1) GUICtrlSetState($SetLimit50, $GUI_CHECKED) $StatusBar = _GUICtrlStatusBar_Create($GUI) _GUICtrlStatusBar_SetParts($StatusBar, $StatusBarSections) _GUICtrlStatusBar_SetText($StatusBar, "Time", 0) _GUICtrlStatusBar_SetText($StatusBar, "00:00:00", 1) _GUICtrlStatusBar_SetText($StatusBar, "Files", 2) _GUICtrlStatusBar_SetText($StatusBar, "0", 3) _GUICtrlStatusBar_SetText($StatusBar, "ID3V1 Tags Found", 4) _GUICtrlStatusBar_SetText($StatusBar, "0", 5) _GUICtrlStatusBar_SetText($StatusBar, "ID3V2 Tags Found", 6) _GUICtrlStatusBar_SetText($StatusBar, "0", 7) $LogoColor = 0x6D6968 GUICtrlCreateLabel("Ian Maxwell", 730, 463, 60, 15) GUICtrlSetColor(-1, $LogoColor) GUICtrlCreateLabel("MaxImuM AdVaNtAgE SofTWarE 2015", 600, 480, 190, 15) GUICtrlSetColor(-1, $LogoColor) $Line = GUICtrlCreateGraphic(680, 497, 110, 2) GUICtrlSetGraphic($Line, $GUI_GR_COLOR, $LogoColor) GUICtrlSetGraphic($Line, $GUI_GR_MOVE, 0, 1) GUICtrlSetGraphic($Line, $GUI_GR_LINE, 110, 1) GUISetState(@SW_SHOW) Do $MSG = GUIGetMsg() Switch $MSG Case $Where $SelectWhere = FileSelectFolder("Please select the MP3 folder to be stripped", -1) GUICtrlSetData($ShowWhere, $SelectWhere) Case $Go $GetWhere = GUICtrlRead($ShowWhere) If FileExists($GetWhere) Then $RemovedID3V1 = 0 $RemovedID3V2 = 0 $FolderLength = StringLen($GetWhere) + 1 GUICtrlSetState($ShowWhere, $GUI_DISABLE) GUICtrlSetState($Where, $GUI_DISABLE) GUICtrlSetState($Go, $GUI_DISABLE) GUICtrlSetState($SetLimit10, $GUI_DISABLE) GUICtrlSetState($SetLimit50, $GUI_DISABLE) GUICtrlSetState($SetLimit150, $GUI_DISABLE) SplashTextOn("Please Wait", "Getting list of files", 250, 50) _GUICtrlListView_DeleteAllItems($FileList) $MP3 = _FileListToArrayRec($GetWhere, "*.mp3", $FLTAR_FILES, $FLTAR_RECUR, $FLTAR_SORT, $FLTAR_FULLPATH) $FileCount = $MP3[0] _GUICtrlStatusBar_SetText($StatusBar, $FileCount, 3) SplashTextOn("Please Wait", "Populating list", 250, 50) _GUICtrlListView_BeginUpdate($FileList) For $a = 1 To $MP3[0] _GUICtrlListView_AddItem($FileList, StringTrimLeft($MP3[$a], $FolderLength), 0) Next _GUICtrlListView_EndUpdate($FileList) If BitAND(GUICtrlRead($SetLimit10), $GUI_CHECKED) Then $FrameLimit = 10 If BitAND(GUICtrlRead($SetLimit50), $GUI_CHECKED) Then $FrameLimit = 50 If BitAND(GUICtrlRead($SetLimit150), $GUI_CHECKED) Then $FrameLimit = 150 SplashOff() $Start = TimerInit() AdlibRegister("__DisplayStats", 1000) __Run() AdlibUnRegister() __DisplayStats() GUICtrlSetState($ShowWhere, $GUI_ENABLE) GUICtrlSetState($Where, $GUI_ENABLE) GUICtrlSetState($Go, $GUI_ENABLE) GUICtrlSetState($SetLimit10, $GUI_ENABLE) GUICtrlSetState($SetLimit50, $GUI_ENABLE) GUICtrlSetState($SetLimit150, $GUI_ENABLE) MsgBox($MB_APPLMODAL, "Done", "Done") Else MsgBox($MB_ICONERROR, "ERROR", "The specified location does not exist, please re-check!") EndIf Case $GUI_EVENT_CLOSE Exit EndSwitch Until True == False Exit Func __Run() For $a = 1 To $MP3[0] $FoundID3V1 = False $ID3V1Count = 0 _GUICtrlListView_EnsureVisible($FileList, $a) $Progress = $a / $MP3[0] * 100 Do $LookForID3V1 = __RemoveV1Tag($MP3[$a]) $ID3V1Ext = @extended & "," If $ID3V1Ext <> 0 Then $ID3V1Count += 1 If $LookForID3V1 == True Then $FoundID3V1 = True EndIf Until $LookForID3V1 == False $FoundID3V2 = False $LookForID3V2 = __RemoveV2Tag($MP3[$a]) If @error == 1 Then _GUICtrlListView_SetItemText($FileList, $a - 1, "Too few frames", 2) ElseIf @error == 2 Then _GUICtrlListView_SetItemText($FileList, $a - 1, "Read error!", 2) Else If $LookForID3V2 == 0 Then _GUICtrlListView_SetItemText($FileList, $a - 1, "None", 2) Else __CutV2Tag($MP3[$a], $LookForID3V2) _GUICtrlListView_SetItemText($FileList, $a - 1, "1st header at " & $LookForID3V2, 2) $RemovedID3V2 += 1 EndIf EndIf If $FoundID3V1 == True Then ; displayed after running so the listview is updated at the same time for ID3V1 and ID3V2 If $ID3V1Count == 1 Then _GUICtrlListView_SetItemText($FileList, $a - 1, "1 Tag", 1) Else _GUICtrlListView_SetItemText($FileList, $a - 1, $ID3V1Count & " Tags", 1) EndIf $RemovedID3V1 += 1 Else _GUICtrlListView_SetItemText($FileList, $a - 1, "None", 1) EndIf $FileCount -= 1 Next EndFunc ;==>__Run Func __RemoveV1Tag($CurrentFile) $hFile = _WinAPI_CreateFile($CurrentFile, 2, 2) $tBuffer = DllStructCreate("byte[3]") Local $FoundV1TAG = 0 For $a = 0 To 2 _WinAPI_SetFilePointer($hFile, $PossibleV1Locations[$a], $FILE_END) _WinAPI_ReadFile($hFile, $tBuffer, 3, $nBytes) $sText = BinaryToString(DllStructGetData($tBuffer, 1)) If $sText == "TAG" Then $FoundV1TAG = $PossibleV1Locations[$a] ExitLoop EndIf Next _WinAPI_CloseHandle($hFile) If $FoundV1TAG < 0 Then $hFile = _WinAPI_CreateFile($CurrentFile, 2, 4) _WinAPI_SetFilePointer($hFile, $FoundV1TAG, $FILE_END) _WinAPI_SetEndOfFile($hFile) _WinAPI_CloseHandle($hFile) SetExtended(Abs($FoundV1TAG)) Return True Else Return False EndIf EndFunc ;==>__RemoveV1Tag Func __RemoveV2Tag($CurrentFile) $Position = 0 $StartPosition = $Position $NumberOfFrames = 0 $hFile = _WinAPI_CreateFile($CurrentFile, 2, 2) $iSize = _WinAPI_GetFileSizeEx($hFile) $tBuffer = DllStructCreate("byte[" & $ChunkSize & "]") Do ;~ look for 1st frame header _WinAPI_SetFilePointer($hFile, $Position, $FILE_BEGIN) $Read = _WinAPI_ReadFile($hFile, $tBuffer, $ChunkSize, $nBytes) If $Read == False Then SetError(2) Return False EndIf $sText = DllStructGetData($tBuffer, 1) For $z = 1 To $ChunkSize - 2 $Pattern = __BitCheck(BinaryMid($sText, $z, 3)) If $Pattern <> False Then $FrameLength = __GetFrameLength(StringMid($Pattern, 12, 2), StringMid($Pattern, 14, 2), StringMid($Pattern, 17, 4), StringMid($Pattern, 21, 2), StringMid($Pattern, 23, 1)) If $FrameLength <> False Then ; possible first frame header found $NumberOfFrames = 1 $DeepPosition = $Position + $z - 1 $DeepFrameLength = $FrameLength $StartPosition = $DeepPosition ;~ check for vbr headers, always a known position after the first valid header _WinAPI_SetFilePointer($hFile, $DeepPosition + 36, $FILE_BEGIN) $tVBRBuffer = DllStructCreate("byte[4]") _WinAPI_ReadFile($hFile, $tVBRBuffer, 4, $nBytes) $sVBR = DllStructGetData($tVBRBuffer, 1) $sVBRText = BinaryToString($sVBR) $LookForXING = StringInStr($sVBRText, "XING") If $LookForXING Then ; no need to look further, correct first header has been found _WinAPI_CloseHandle($hFile) Return $DeepPosition + $LookForXING - 1 Else $LookForVBRI = StringInStr($sVBRText, "VBRI") If $LookForVBRI Then ; no need to look further, correct first header has been found _WinAPI_CloseHandle($hFile) Return $DeepPosition + $LookForVBRI - 1 EndIf EndIf $DeepRead = ($FrameLimit - 1) * ($DeepFrameLength + 4) _WinAPI_SetFilePointer($hFile, $DeepPosition, $FILE_BEGIN) $tBuffer2 = DllStructCreate("byte[" & $DeepRead & "]") $Read2 = _WinAPI_ReadFile($hFile, $tBuffer2, $DeepRead, $nBytes) If $Read2 == False Then SetError(2) Return False EndIf $sText2 = DllStructGetData($tBuffer2, 1) $DeeperPosition = 1 For $Look = 1 To $FrameLimit - 1 ; look for subsequent headers $Pattern = __BitCheck(BinaryMid($sText2, $DeeperPosition, 3)) If $Pattern <> False Then $DeepFrameLength = __GetFrameLength(StringMid($Pattern, 12, 2), StringMid($Pattern, 14, 2), StringMid($Pattern, 17, 4), StringMid($Pattern, 21, 2), StringMid($Pattern, 23, 1)) If $FrameLength == False Then $NumberOfFrames = 0 ExitLoop Else If $FrameLength > 0 Then $NumberOfFrames += 1 $DeeperPosition += $DeepFrameLength Else $NumberOfFrames = 0 ExitLoop EndIf EndIf Else $NumberOfFrames = 0 ExitLoop EndIf If $Position > $iSize - 3 Then ExitLoop Next If $NumberOfFrames == $FrameLimit Then _WinAPI_CloseHandle($hFile) Return $StartPosition EndIf EndIf EndIf Next $Position += $ChunkSize - 4 Until $Position > $iSize - 4 _WinAPI_CloseHandle($hFile) SetError(1) Return False EndFunc ;==>__RemoveV2Tag Func __BitCheck($sString) $sPattern = "" $sByte = BinaryMid($sString, 1, 1) For $a = 7 To 0 Step -1 If BitAND($sByte, $BitValue[$a]) Then $sPattern &= "1" Else $sPattern &= "0" EndIf Next If $sPattern == "11111111" Then $sByte = BinaryMid($sString, 2, 1) For $a = 7 To 0 Step -1 If BitAND($sByte, $BitValue[$a]) Then $sPattern &= "1" Else $sPattern &= "0" EndIf Next If StringLeft($sPattern, 11) == "11111111111" Then $sByte = BinaryMid($sString, 3, 1) For $a = 7 To 0 Step -1 If BitAND($sByte, $BitValue[$a]) Then $sPattern &= "1" Else $sPattern &= "0" EndIf Next Return $sPattern EndIf EndIf Return False EndFunc ;==>__BitCheck Func __GetFrameLength($sVersion, $sLayer, $sRate, $sSample, $sPad) ;~ ConsoleWrite("ver " & $sVersion & " layer " & $sLayer & " rate " & $sRate & " sample " & $sSample & " pad " & $sPad & @CR) ;~ vesion ;~ 00 - MPEG Version 2.5 ;~ 01 - reserved ;~ 10 - MPEG Version 2 (ISO/IEC 13818-3) ;~ 11 - MPEG Version 1 (ISO/IEC 11172-3) ;~ layer ;~ 00 - reserved ;~ 01 - Layer III ;~ 10 - Layer II ;~ 11 - Layer I If $sVersion == "01" Then ; reserved Return False EndIf If $sLayer == "00" Then ; reserved Return False EndIf If $sRate == "1111" Or $sRate == "0000" Then ; bad, reserved Return False EndIf ; determine cell padding size If $sPad == "0" Then $sPaddingAdded = 0 Else If $sLayer == "11" Then ; Layer 1 $sPaddingAdded = 4 ElseIf $sLayer == "10" Or $sLayer == "01" Then ; Layer 2 & Layer 3 $sPaddingAdded = 1 EndIf EndIf ; determine the sample rate Local $SampleBits = 0 For $a = 0 To 1 If StringMid($sSample, $a + 1, 1) == 1 Then $SampleBits += $SampleConvert[$a] Next Switch $sVersion Case "11" ; MPEG V1 $SampleGroup = 0 Case "10" ; MPEG V2 $SampleGroup = 1 Case "00" ; MPEG V2.5 $SampleGroup = 2 EndSwitch Local $GetSampleFromIndex = $SampleArray[$SampleBits][$SampleGroup] ; determine the bitrate Local $BitrateBits = 0 For $a = 0 To 3 If StringMid($sRate, $a + 1, 1) == 1 Then $BitrateBits += $IndexConvert[$a] Next Switch $sVersion Case "11" ; MPEG V1 Switch $sLayer Case "11" ; Layer 1 $Group = 0 Case "10" ; Layer 2 $Group = 1 Case "01" ; Layer 3 $Group = 2 EndSwitch Case "10" ; MPEG V2 Switch $sLayer Case "11" ; Layer 1 $Group = 3 Case "10" ; Layer 2 $Group = 4 Case "01" ; Layer 3 $Group = 4 EndSwitch Case "00" ; MPEG V2.5 Switch $sLayer Case "11" ; Layer 1 $Group = 3 Case "10" ; Layer 2 $Group = 4 Case "01" ; Layer 3 $Group = 4 EndSwitch EndSwitch Local $GetBitrateFromIndex = $IndexArray[$BitrateBits][$Group] ; determine the samples per frame Switch $sLayer Case "01" ; Layer 3 If $sVersion == "11" Then ; MPEG V1 $SamplesPerFrame = 144 ; 1152 / 8, simplified for below calculation Else ; MPEG V2 and MPEG V2.5 $SamplesPerFrame = 72 ; 576 / 8, simplified for below calculation EndIf Case "10" ; Layer 2 $SamplesPerFrame = 144 ; 1152 / 8, simplified for below calculation Case "11" ; Layer 1 $SamplesPerFrame = 48 ; 384 / 8, simplified for below calculation EndSwitch $sResult = Floor(($SamplesPerFrame * $GetBitrateFromIndex / $GetSampleFromIndex) + $sPaddingAdded) If $sResult < 0 Then Return False Else Return $sResult EndIf EndFunc ;==>__GetFrameLength Func __CutV2Tag($CurrentFile, $sPosition) If $sPosition > 0 Then $hFile = _WinAPI_CreateFile($CurrentFile, 2, 2) $iSize = _WinAPI_GetFileSizeEx($hFile) $tBuffer = DllStructCreate("byte[" & $iSize - $sPosition & "]") $Point = _WinAPI_SetFilePointer($hFile, $sPosition, $FILE_BEGIN) $Read = _WinAPI_ReadFile($hFile, $tBuffer, $iSize - $sPosition, $nBytes) $sText = DllStructGetData($tBuffer, 1) _WinAPI_CloseHandle($hFile) $hFile = _WinAPI_CreateFile($CurrentFile, 4, 4) $Point = _WinAPI_SetFilePointer($hFile, 0, $FILE_BEGIN) $Write = _WinAPI_WriteFile($hFile, $tBuffer, $iSize - $sPosition, $nBytes) _WinAPI_CloseHandle($hFile) EndIf EndFunc ;==>__CutV2Tag Func __DisplayStats() $ElapsedSeconds = Int(TimerDiff($Start) / 1000) $ElapsedMinutes = 0 $ElapsedHours = 0 Do If $ElapsedSeconds > 59 Then $ElapsedSeconds -= 60 $ElapsedMinutes += 1 EndIf Until $ElapsedSeconds < 60 Do If $ElapsedMinutes > 59 Then $ElapsedMinutes -= 60 $ElapsedHours += 1 EndIf Until $ElapsedMinutes < 60 _GUICtrlStatusBar_SetText($StatusBar, StringFormat('%.2i', $ElapsedHours) & ":" & StringFormat('%.2i', $ElapsedMinutes) & ":" & StringFormat('%.2i', $ElapsedSeconds), 1) _GUICtrlStatusBar_SetText($StatusBar, $FileCount, 3) _GUICtrlStatusBar_SetText($StatusBar, $RemovedID3V1, 5) _GUICtrlStatusBar_SetText($StatusBar, $RemovedID3V2, 7) GUICtrlSetData($TotslProgress, $Progress) EndFunc ;==>__DisplayStats