Jump to content
Sign in to follow this  
ripdad

_Mp3_TrimFrames

Recommended Posts

ripdad

An attempt at a mp3 trimmer in AutoIt.

If anyone knows a better way .. please, be my guest.

#cs
    AutoIt Version 3.3.6
    
    _Mp3_TrimFrames v.1.80 by ripdad
    Released: July 04, 2010
     Updated: July 12, 2010
    
    - Modes:
    $nMode 1 = Trim beginning
    $nMode 2 = Trim end
    
    - Accuracy without offset: +/- 2 Seconds
    
    - Notes:
    VBR not supported
    Tags other than ID3v1 and ID3v2 not supported
#ce

; Example
$mp3File = FileOpenDialog("Select File", @ScriptDir, "Select Mp3 File (*.mp3)", 1)
If Not $mp3File Then Exit
_Mp3_TrimFrames($mp3File, 1, 60); <-- Number of seconds to trim

If @error <> 1 Then MsgBox(16, '_Mp3_TrimFrames', 'Trim Failed ' & @error)
Exit

Func _Mp3_TrimFrames($sFile, $nMode, $nSeconds = 0, $offset = 0); <-- Offset can be (+ or -) 1 or 2 seconds
    If Not FileExists($sFile) Then Return SetError(-1)
    If Not ($nMode == 1 Or $nMode == 2) Then Return SetError(-2); Invalid Mode
    If $nSeconds < 1 Then Return SetError(-3); Must Be a Positive Number
    ;
    Local $lcArray = _GetMP3Info($sFile); <-- Thanks to Lazycat
    If $lcArray = '' Then Return SetError(-4); Frame Not Found
    If $lcArray[1] = 1 Then Return SetError(-5); VBR Not Supported
    Local $fo = FileOpen($sFile, 16)
    If $fo = -1 Then Return SetError(-6); Failed to Open File
    Local $fr = FileRead($fo)
    FileClose($fo)
    ;
    Local $ss = StringSplit(StringReplace($fr, $lcArray[2], '|' & $lcArray[2]), '|'); Load Array
    Local $String, $nTrim = Round(($ss[0] / $lcArray[3]) * ($nSeconds + $offset))
    If $ss[0] < 200 Then Return SetError(-7); Invalid Frame Count (Probably Corrupt Header)
    If $ss[0] < $nTrim Then Return SetError(-8); Trim Number Exceeds Total Frames
    ;
    If $nMode = 1 Then; Omit Beginning Frames
        $String = $ss[1]; Add First Frame
        For $i = $nTrim To $ss[0]
            $String &= $ss[$i]
        Next
    EndIf
    ;
    If $nMode = 2 Then; Omit Ending Frames
        For $i = 1 To $ss[0] - $nTrim
            $String &= $ss[$i]
        Next
        $String &= $ss[$ss[0]]; Add Last Frame
    EndIf
    ;
    Local $tName = StringTrimLeft($sFile, StringInStr($sFile, '\', 1, -1)); Get FileName
    Local $sPath = StringTrimRight($sFile, StringLen($tName)); Get Path
    $fo = FileOpen($sPath & 'Trimmed-' & $tName, 18)
    If $fo = -1 Then Return SetError(-9)
    If FileWrite($fo, $String) Then FileClose($fo); New file written - Original file stays intact
    ;
    ; Check trimmed file if written properly
    If StringLeft(FileRead($sPath & 'Trimmed-' & $tName), 2) = '0x' Then
        FileDelete($sPath & 'Trimmed-' & $tName)
        Return SetError(-10)
    EndIf
    Return SetError(1); Success
EndFunc
;
;
;===============================================================================
;
; Description:      Retrieve MP3 (MP2, MPA) basic information
; Parameter(s):     File name
; Requirement(s):   None
; Return Value(s):  On Success - array with data:
;                       0 - MPEG version
;                       1 - Layer
;                       2 - Bitrate
;                       3 - Frequency
;                       4 - Channel Mode
;                       5 - Duration
;                       6 - Frames
;                       7 - CRC protected
;                       8 - Copyrighted
;                       9 - Original
;                   On Failure empty string and sets @ERROR:
;                       1 - Info not found
; Author(s):        YDY (Lazycat) <mpc@nm.ru>
; Version:          1.2.00
; Date:             30.12.2004
; Note(s):          None
;
;===============================================================================
Func _GetMP3Info($file)
    Local $data[10], $offset = 1, $isValid = 0, $isVBR = 0
    Local $aVersion = StringSplit("MPEG 2.5|Undefined|MPEG 2.5|MPEG 1", "|")
    Local $aLayer = StringSplit("Undefined|Layer III|Layer II|Layer I", "|")
    Local $sBitrate = ""
    Local $sFrequency = ""
    Local $aChanMode = StringSplit("Stereo|Joint stereo|Dual channel|Mono", "|")
    Local $aFlags = StringSplit("No|Yes", "|")
    Local $head, $HeadID, $nVer, $nLay

    If _FileReadAtOffsetHEX($file, 1, 3) = "494433" Then ; ID3v2 tag found
        $offset = _HEXToSSInt(_FileReadAtOffsetHEX($file, 7, 4)) + 10 ; End of ID3v2 tag
    EndIf

    For $ic = $offset To 4096 + $offset
        $marker = _FileReadAtOffsetHEX($file, $ic, 2)
        $marker = StringLeft($marker, 3)
        If StringInStr("FFF, FFE", $marker) Then ; Assume that is frame start
            $head = _HexToBin(_FileReadAtOffsetHEX($file, $ic, 4))
            $nVer = _GetRBits($head, 19, 2)
            $nLay = _GetRBits($head, 17, 2)
            If ($nVer <> 1) And ($nLay <> 0) Then
                If _FileReadAtOffsetHEX($file, $ic + 36, 4) = "58696E67" Then $isVBR = 1 ; Is this a right way?..
                $isValid = 1
                ExitLoop
            EndIf
        EndIf
    Next

    If Not $isValid Then
        SetError(1) ; Frame not found (not mp3 data?)
        Return ("")
    EndIf

    Select
        Case $nVer = 3
            $sFrequency = "44100|48000|32000|Undefined"
        Case $nVer = 2
            $sFrequency = "22050|24000|16000|Undefined"
        Case $nVer = 0
            $sFrequency = "11025|12000|8000|Undefined"
    EndSelect
    Local $aFrequency = StringSplit($sFrequency, "|")
    $data[3] = _GetData($aFrequency, _GetRBits($head, 10, 2))

    Local $pad = 0, $bitrate, $framesize, $frames, $length, $fps
    If _GetRBits($head, 9, 1) Then $pad = 1

    If $isVBR Then
        $data[2] = "Variable"
    Else
        Select
            Case $nVer = 3 And $nLay = 3
                $sBitrate = "Free|32|64|96|128|160|192|224|256|288|320|352|384|416|448|Undefined"
            Case $nVer = 3 And $nLay = 2
                $sBitrate = "Free|32|48|56|64|80|96|112|128|160|192|224|256|320|384|Undefined"
            Case $nVer = 3 And $nLay = 1
                $sBitrate = "Free|32|40|48|56|64|80|96|112|128|160|192|224|256|320|Undefined"
            Case $nVer = 2 And $nLay = 3
                $sBitrate = "Free|32|48|56|64|80|96|112|128|144|160|176|192|224|256|Undefined"
            Case ($nVer = 2 And $nLay = 2) Or ($nVer = 2 And $nLay = 1)
                $sBitrate = "Free|8|16|24|32|40|48|56|64|80|96|112|128|144|160|Undefined"
        EndSelect
        Local $aBitrate = StringSplit($sBitrate, "|")
        $data[2] = _GetData($aBitrate, _GetRBits($head, 12, 4))
        $bitrate = 1000 * $data[2]
        If $nLay = 3 Then
            $framesize = (((12 * $bitrate) / $data[3]) + $pad) * 4
            $fps = $data[3] / 384
        Else
            $framesize = ((144 * $bitrate) / $data[3]) + $pad
            $fps = $data[3] / 1152
        EndIf
        $frames = FileGetSize($file) / $framesize
        $length = $frames / $fps
    EndIf

    Return StringSplit($isVBR & '|' & StringLeft(_BinToHex($head), 5) & '|' & $length, "|"); <- This line added by ripdad

    $data[0] = _GetData($aVersion, $nVer)
    $data[1] = _GetData($aLayer, $nLay)
    $data[4] = _GetData($aChanMode, _GetRBits($head, 6, 2))
    $data[5] = StringFormat("%d:%02d", Int($length / 60), $length - Int($length / 60) * 60)
    $data[6] = Int($frames)
    $data[7] = _GetData($aFlags, Not _GetRBits($head, 16, 1)) ; CRC
    $data[8] = _GetData($aFlags, _GetRBits($head, 3, 1)) ; Private
    $data[9] = _GetData($aFlags, _GetRBits($head, 2, 1)) ; Original
    ;Return ($data)
EndFunc

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Support functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Func _GetRBits($str, $pos, $size)
    Local $ic, $res = 0, $bStr = StringMid($str, 33 - $pos - $size, $size)
    For $ic = 0 To $size - 1
        If StringMid($bStr, $size - $ic, 1) == "1" Then $res = $res + 2 ^ $ic
    Next
    Return ($res)
EndFunc

Func _GetData(ByRef $array, $val)
    If $val > UBound($array) - 1 Then Return ("Undefined")
    Return ($array[$val + 1])
EndFunc

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Common functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Func _HEXToSSInt($sHexStr)
    Local $iCnt, $iLen, $sTempStr = "", $iReturn = 0
    For $iCnt = 1 To StringLen($sHexStr) Step 2
        $sTempStr = $sTempStr & StringTrimLeft(_HexToBin(StringMid($sHexStr, $iCnt, 2)), 1)
    Next
    $iLen = StringLen($sTempStr)
    For $iCnt = 0 To $iLen - 1
        $iReturn = $iReturn + Number(StringMid($sTempStr, $iLen - $iCnt, 1)) * 2 ^ $iCnt
    Next
    Return ($iReturn)
EndFunc

Func _HexToBin($str)
    Local $res = "", $i
    While StringLen($str) > 0
        $val = Dec(StringRight($str, 1))
        $str = StringTrimRight($str, 1)
        For $i = 1 To 4
            $res = String(Mod($val, 2)) & $res
            $val = Int($val / 2)
        Next
    WEnd
    Return ($res)
EndFunc

Func _FileReadAtOffsetHEX($file, $offset, $bytes)
    Local $tfile = FileOpen($file, 0)
    Local $tstr = "", $i
    FileRead($tfile, $offset - 1)
    For $i = $offset To $offset + $bytes - 1
        $tstr = $tstr & Hex(Asc(FileRead($tfile, 1)), 2)
    Next
    FileClose($tfile)
    Return ($tstr)
EndFunc
;
; Author: joeyb1275 - Modified by ripdad
Func _BinToHex($BinString)
    Local $Rev, $Hex = ''
    If Not IsInt(StringLen($BinString) / 4) Then
        $Num = ((StringLen($BinString) / 4) - Int(StringLen($BinString) / 4)) * 4
        For $i = 1 To 4 - $Num Step 1
            $BinString = '0' & $BinString
        Next
    EndIf
    For $i = 4 To StringLen($BinString) Step 4
        $Bin = StringLeft(StringRight($BinString, $i), 4)
        Switch $Bin
            Case '0000'
                $Hex &= '|0'
            Case '0001'
                $Hex &= '|1'
            Case '0010'
                $Hex &= '|2'
            Case '0011'
                $Hex &= '|3'
            Case '0100'
                $Hex &= '|4'
            Case '0101'
                $Hex &= '|5'
            Case '0110'
                $Hex &= '|6'
            Case '0111'
                $Hex &= '|7'
            Case '1000'
                $Hex &= '|8'
            Case '1001'
                $Hex &= '|9'
            Case '1010'
                $Hex &= '|A'
            Case '1011'
                $Hex &= '|B'
            Case '1100'
                $Hex &= '|C'
            Case '1101'
                $Hex &= '|D'
            Case '1110'
                $Hex &= '|E'
            Case '1111'
                $Hex &= '|F'
            Case Else
                SetError(-1)
        EndSwitch
    Next
    If @error Then Return -1
    $Rev = StringSplit(StringTrimLeft($Hex, 1), '|')
    $Hex = ''
    For $i = $Rev[0] To 1 Step -1
        $Hex &= $Rev[$i]
    Next
    Return $Hex
EndFunc
;

Update: July 08, 2010 - Found a solution for the frame recognition limitation. Added a fileopen dialog.

Update: July 12, 2010 - A few minor improvements. Also, I'm done with this. Off to other projects ...

Edited by ripdad

"The mediocre teacher tells. The Good teacher explains. The superior teacher demonstrates. The great teacher inspires." -William Arthur Ward

Share this post


Link to post
Share on other sites
ripdad

- Update -

Converted frames to seconds -

plus a few other changes


"The mediocre teacher tells. The Good teacher explains. The superior teacher demonstrates. The great teacher inspires." -William Arthur Ward

Share this post


Link to post
Share on other sites
JohnOne

Trim Failed? wtf?

Perhaps you should expand on your post.

How does this help the OP in any way what so ever, to debug his code?

In fact you shouldnt even bother replying to this without relevant info about your findings.


AutoIt Absolute Beginners    Require a serial    Pause Script    Video Tutorials by Morthawt   ipify 

Monkey's are, like, natures humans.

Share this post


Link to post
Share on other sites
ripdad

Trim Failed? wtf?

There's 11 error codes in the script.

Which one is it?

Also, you can find out what happened just by looking at the SetError line.


"The mediocre teacher tells. The Good teacher explains. The superior teacher demonstrates. The great teacher inspires." -William Arthur Ward

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.