Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Powershell editing mp3 infos

Tags:

powershell

mp3

I'm searching for a way to edit mp3 files info (like artist, album, etc) in a PowerShell script.

I found a way to get the info I need on the .mp3 files, but not how to modify them.

$songs = Get-ChildItem $dir -Filter *.mp3;
$shell = new-object -com shell.application;

Foreach ($song in $songs) {
    $shellfolder = $shell.namespace($dir);
    $shellfile = $shellfolder.parsename($song);

    $title = $shell.namespace($dir).getdetailsof($shellfile,21);
}

With getDetailsOf (with 21) I'm able to get the song Title, but no setDetailsOf exists, so I don't know how I can modify the song title for another one.

like image 603
Felix4 Avatar asked Aug 02 '26 04:08

Felix4


1 Answers

If you're like me and prefer not to use a library, here's a pretty simple ID3v1 function I wrote that may serve your needs:

    #Set the specified ID3v1 properties of a file by writing the last 128 bytes
    Function Set-ID3v1( #All parameters except path are optional, they will not change if not specified. 
      [string]$path, #Full path to the file to be updated - wildcards not supported because [] are so stinky and it's only supposed to work on one file at a time. 
      [string]$Title = "`0", #a string containing only 0 indicates a parameter not specified. 
      [string]$Artist  = "`0",
      [string]$Album = "`0",
      [string]$Year = "`0",
      [string]$Comment = "`0",
      [int]$Track = -1,
      [int]$Genre = -1, 
      [bool]$BackDate=$true){#Preserve modification date, but add a minute to indicate it's newer than duplicates
        $CurrentModified = (Get-ChildItem -LiteralPath $path).LastWriteTime #use literalpath here to get only one file, even if it has []
        Try{
            $enc = [System.Text.Encoding]::ASCII #Probably wrong, but works occasionally. See https://stackoverflow.com/questions/9857727/text-encoding-in-id3v2-3-tags
            $currentID3Bytes = New-Object byte[] (128)
            $strm = New-Object System.IO.FileStream ($path,[System.IO.FileMode]::Open,[System.IO.FileAccess]::ReadWrite,[System.IO.FileShare]::None)
            $strm.Seek(-128,'End') | Out-Null #Basic ID3v1 info is 128 bytes from EOF
            $strm.Read($currentID3Bytes,0,$currentID3Bytes.Length) | Out-Null
            Write-Host "$path `nCurrentID3: $($enc.GetString($currentID3Bytes))"
            $strm.Seek(-128,'End') | Out-Null #Basic ID3v1 info is 128 bytes from EOF
            If($enc.GetString($currentID3Bytes[0..2]) -ne  'TAG'){
                Write-Warning "No existing ID3v1 found - adding to end of file"
                $strm.Seek(0,'End') 
                $currentID3Bytes = $enc.GetBytes(('TAG' + (' ' * (30 + 30 + 30 + 4 + 30)))) #Add a blank tag to the end of the file
                $currentID3Bytes += 255 #empty Genre
                $strm.Write($currentID3Bytes,0,$currentID3Bytes.length)
                $strm.Flush()
                $Strm.Close()
                $strm = New-Object System.IO.FileStream ($path,[System.IO.FileMode]::Open,[System.IO.FileAccess]::Write,[System.IO.FileShare]::None)
                $strm.Seek(-128,'End') 
            } 
            $strm.Seek(3,'Current') | Out-Null #skip over 'TAG' to get to the good stuff
            If($Title -eq "`0"){ $strm.Seek(30,'Current') | Out-Null} #Skip over
             Else{ $strm.Write($enc.GetBytes($Title.PadRight(30,' ').Substring(0,30)),0,30)  } #if specified, write 30 space-padded bytes to the stream
            If($Artist -eq "`0"){ $strm.Seek(30,'Current') | Out-Null} 
             Else {$strm.Write($enc.GetBytes($Artist.PadRight(30,' ').Substring(0,30)),0,30) }
            If($Album -eq "`0"){ $strm.Seek(30,'Current') | Out-Null} 
             Else{$strm.Write($enc.GetBytes($Album.PadRight(30,' ').Substring(0,30)),0,30)  }
            If($Year -eq "`0"){ $strm.Seek(4,'Current') | Out-Null} 
             Else {$strm.Write($enc.GetBytes($Year.PadRight(4,' ').Substring(0,4)),0,4) }
            If(($Track -ne -1) -or ($currentID3Bytes[125] -eq 0)) {$CommentMaxLen = 28}Else{$CommentMaxLen = 30} #If a Track is specified or present in the file, Comment is 28 chars
            If($Comment -eq "`0"){ $strm.Seek($CommentMaxLen,'Current') | Out-Null} 
             Else {$strm.Write($enc.GetBytes($Comment.PadRight($CommentMaxLen,' ').Substring(0,$CommentMaxLen)),0,$CommentMaxLen)  }
            If($Track -eq -1 ){$strm.Seek(2,'Current') | Out-Null}
             Else{$strm.Write(@(0,$Track),0,2)} #Track, if present, is preceded by a 0-byte to form the last two bytes of Comment
            If($Genre -ne -1){$strm.Write($Genre,0,1) | Out-Null} 
        }Catch{
            Write-Error $_.Exception.Message
        }Finally{
            If($strm){
                $strm.Flush()
                $strm.Close()
            }
        }
        If($BackDate){(Get-ChildItem -LiteralPath $path).LastWriteTime = $CurrentModified.AddMinutes(1)}
    }

You call it with the full path to the MP3 file and any ID3V1 attributes you want to change:

    Set-ID3v1 -path "c:\users\me\Desktop\Test.mp3" -Year 1996 -Title "This is a test" 

Basically it writes the last 128 bytes of the file with Title, Artist, etc. I might add support for ID3v2.3 which is much more flexible, but less compatible with older devices and software. Check GitHub for the latest version.

like image 191
Rich Moss Avatar answered Aug 03 '26 17:08

Rich Moss



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!