6

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.

RBT
  • 24,161
  • 21
  • 159
  • 240
Felix4
  • 185
  • 4
  • 10
  • 3
    Have you tried working with mp3 tags? This modest blogger has found a way to do it with Powershell. [Todd Klindt's blog](http://www.toddklindt.com/blog/Lists/Posts/Post.aspx?ID=468) – MonkeyDreamzzz May 07 '15 at 15:31
  • Thank you Rubanov that is exactly what I needed. It worked like a charm! – Felix4 May 08 '15 at 15:52

1 Answers1

5

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.

Rich Moss
  • 2,195
  • 1
  • 13
  • 18
  • is there no simple way to access the extended attributes (`company`, `author`, `copyright`, `legal trademark`, etc) of a given file type such as a font file?? i have no idea how to use powershell scrips, but i was going to use a `child_process` in nodejs to get access to an instance of powershell or cmd – oldboy Jan 22 '20 at 06:50
  • The script above is intended only for MP3 ID3v1 info which is embedded in the last 128 bytes of a file. Company, Author, etc. are not part of the [ID3v1 spec](http://id3.org/ID3v1). Have you looked at [this question](https://stackoverflow.com/questions/26091836/is-there-a-way-to-read-file-metadata-using-node-js)? – Rich Moss Jan 22 '20 at 19:32
  • yes i have looked at that Q already and no dice. those extended attributes i mentioned correspond to font files. u know of any way to obtain them? – oldboy Jan 23 '20 at 02:07
  • The [Shell.Application](https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/bb776890(v%3Dvs.85) object might help you find the Font properties. [Here's a script I wrote](https://github.com/mossrich/PowershellRecipes/blob/master/Get-ShellApplicationPropertiesAndVerbs.ps1) to get you started. – Rich Moss Jan 23 '20 at 22:58
  • thanks rich how do i run shell scripts like those in node tho? – oldboy Jan 24 '20 at 06:00
  • I'm concerned that we're getting off-topic - the original question was about Powershell and MP3 metadata. I'm not an expert in Node, and can't confirm that the Shell.Application object is even available for a Node.js application. You're likely to get better answers if you create a new question with the proper tags: node.js and font. – Rich Moss Jan 24 '20 at 19:04
  • i will do that eventually if need be – oldboy Jan 24 '20 at 22:11