PowerShell - Changing MP4 Metadata (Right Click Properties)

2.4k views Asked by At

I've been trying to find a way to edit the "Tags" section by swapping it with the "Title" section for MP4.

The problem is that I can't do this with ExiftTool, MP3Tag, MediaMonkey, and it's a nightmare trying to manually right click and change the Tags for every single file.

Example Of File Properties

The closest I came was an app called MP4 Video & Auto Tag Editor since it lets you edit Microsoft Xtra Atoms, but alas...it doesn't have a way to do this for an entire directory

I found this PowerShell script, and it shows all the metadata.

However, I don't know how to exactly edit the "Tags" section to actually customize and trade values. I know ExiftTool could only "read' and not "write" so my biggest fear is that I face the same issue.

The SRCDIRECTORY represents the directory I'm using...

Function Get-MP3MetaData 
{ 
    [CmdletBinding()] 
    [Alias()] 
    [OutputType([Psobject])] 
    Param 
    ( 
        [String] [Parameter(Mandatory=$true, ValueFromPipeline=$true)] $Directory 
    ) 
 
    Begin 
    { 
        $shell = New-Object -ComObject "Shell.Application" 
    } 
    Process 
    { 
 
        Foreach($Dir in $Directory) 
        { 
            $ObjDir = $shell.NameSpace($Dir) 
            $Files = gci $Dir| ?{$_.Extension -in '.mp3','.mp4'} 
 
            Foreach($File in $Files) 
            { 
                $ObjFile = $ObjDir.parsename($File.Name) 
                $MetaData = @{} 
                $MP3 = ($ObjDir.Items()|?{$_.path -like "*.mp3" -or $_.path -like "*.mp4"}) 
                $PropertArray = 0,1,2,12,13,14,15,16,17,18,19,20,21,22,27,28,36,220,223 
             
                Foreach($item in $PropertArray) 
                {  
                    If($ObjDir.GetDetailsOf($ObjFile, $item)) #To avoid empty values 
                    { 
                        $MetaData[$($ObjDir.GetDetailsOf($MP3,$item))] = $ObjDir.GetDetailsOf($ObjFile, $item) 
                    } 
                  
                } 
             
                New-Object psobject -Property $MetaData |select *, @{n="Directory";e={$Dir}}, @{n="Fullname";e={Join-Path $Dir $File.Name -Resolve}}, @{n="Extension";e={$File.Extension}} 
            } 
        } 
    } 
    End 
    { 
    } 
} 


ForEach($item in ("SRCDIRECTORY" |Get-MP3MetaData)){ 
    $itemTitle = $item.Title 
    $itemTags = $item.Tags
}
1

There are 1 answers

0
aolszowka On

While I have not been able to find a "pure" PowerShell way to do this, there is a similar post related to how to do this in C# Here: https://stackoverflow.com/a/24053204/433069 The gist of this persons answer was to use the (historically Microsoft Supported) Windows® API Code Pack for Microsoft® .NET Framework and then use this snippet (this is the C# version of this):

var shellFile = ShellFile.FromParsingName(filePath);
ShellPropertyWriter w = shellFile.Properties.GetPropertyWriter();
w.WriteProperty(SystemProperties.System.Title, "New Title");
w.Close();

The problem is that this API Code pack is no longer found (at least in my searches) to be provided by Microsoft. However some developer copied the source as it existed and posted it up on GitHub here.

This provides interesting insight into how Microsoft would have done this.

If you look at the C# code you'll see that eventually they'll call down into the following Shell classes IShellItem2::GetPropertyStore and then call IPropertyStore::SetValue however these are the C++ API's which do not appear to be exposed via COM, which means (as far as I know) is no straight forward way within PowerShell to accomplish this.

There is an interesting discussion here Is it possible to get shell properties for an item not in the shell namespace? wherein this developer was attempting to access this interface in various ways without much luck.The solution provided there I believe will only get you read access to the properties (I'd love to stand corrected).

In theory you could rewrite the relevant portions of the Windows API Codepack and then Add-Type them in as required if you want to ship "Batteries Included". However for me, I just wanted to get something working.

Therefore I pulled the two NuGet packages produced by that project (windowsapicodepack-core.1.1.2 and windowsapicodepack-shell.1.1.1) extracted the DLL's (Microsoft.WindowsAPICodePack.dll and Microsoft.WindowsAPICodePack.Shell.dll) into the same path as my Script and then wrote up the following:

$targetFile = "C:\temp\Sample.mp4"

Add-Type -Path $PSScriptRoot\Microsoft.WindowsAPICodePack.Shell.dll

$shellFile = [Microsoft.WindowsAPICodePack.Shell.ShellFile]::FromFilePath($targetFile)
$propertyWriter = $shellFile.Properties.GetPropertyWriter()
$titlePropertyKey = [Microsoft.WindowsAPICodePack.Shell.PropertySystem.PropertyKey]::new([Guid]::new("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 2)
$propertyWriter.WriteProperty($titlePropertyKey, "New Title")
$propertyWriter.Close()

Its a rough transliteration of the C# code. The big kicker was I was unable to get the static property type (Microsoft.WindowsAPICodePack.Shell.PropertySystem.SystemProperties.System.Title) to load in, so I just ended up recreating the PropertyKey as the static class did.

I can confirm that this works on the few sample files I had.