Set-Media.ps1

function Set-Media
{
    <#
    .Synopsis
        Sets media metadata
    .Description
        Sets media metadata, using ffmpeg
    .Example
        Set-Media -InputPath $home\Music\ASong.mp3 -Property @{
            Title = "A Song"
            Artist = "An Artist"
            Lyrics = "Some lyrics"
        }
    .Example
        Set-Media -InputPath $home\Music\ASong.mp3 -AlbumArt $home\Music\Cover.jpg
    .Link
        Get-Media
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([Nullable])]
    param(
    # The Input Path
    [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
    [Alias('Fullname')]
    [string[]]
    $InputPath,

    # A dictionary of properties to update
    [Parameter(ValueFromPipeline)]
    [Alias('InputObject')]
    [PSObject[]]
    $Property,

    # A collection of properties to clear
    [string[]]
    $ClearProperty,

    # The path to album art.
    [string]
    $AlbumArt,

    # The type of album art. By default 'Cover (front)'
    [string]
    $AlbumArtType = 'Cover (front)',

    # The path to FFMpeg.exe. Download it from http://ffmpeg.org/
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [string]
    $FFMpegPath
    )


    begin {
        $allInputsFiles = [Collections.ArrayList]::new()
        $allInputObjects = [Collections.ArrayList]::new()
        #region Find ffmpeg
        $ffmpeg = Get-FFMpeg -ffMpegPath $ffMpegPath
        #endregion Find ffmpeg

        $xmlPropertyNames = 'LocalName','NamespaceURI','Prefix',
            'NodeType','ParentNode','OwnerDocument','IsEmpty',
            'Attributes','HasAttributes','SchemaInfo',
            'InnerXml','InnerText','NextSibling','PreviousSibling',
            'Value','ChildNodes','FirstChild','LastChild','HasChildNodes',
            'IsReadOnly','OuterXml','BaseURI','PreviousText'
    }

    process {
        $allInputsFiles.AddRange($InputPath)
        if ($Property) {
            $allInputObjects.AddRange($Property)
        }
    }

    end {
        $c, $t, $id = 0, $allInputsFiles.Count, [Random]::new().Next()
        foreach ($in in $allInputsFiles) {

            #region Resolve the Input Path
            $ri =
                if ([IO.File]::Exists($In)) {
                    $In
                } else {
                    $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($In) |
                        Get-Item -LiteralPath { $_} |
                        Select-Object -ExpandProperty Fullname
                }
            if (-not $ri) { continue }
            #endregion Resolve the Input Path

            $inputObject = $allInputObjects[$c]
            Write-Progress "Setting Metadata" "$ri" -PercentComplete ($c * 100 / $t) -Id $id
            $c++

            $inFile = "$ri"
            $inFileInfo = [IO.FileInfo]$inFile
            $tempFileName = Join-Path $inFileInfo.Directory.FullName ('_temp_' + $inFileInfo.Name)

            $copyArgs = @('-c', 'copy')

            $metadataArgs = @(
                if ($ClearProperty -match '\*') {
                    $originalMetadata = Get-Media -InputPath $inFile
                    foreach ($prop in $originalMetadata.psobject.properties) {
                        $matchedFilter = foreach ($cp in $ClearProperty) {
                            if ($prop.Name -like $cp) {
                                $true;break
                            }
                        }

                        if ($matchedFilter) {
                            '-metadata'
                            '' + $prop.Name + '=""'
                        }
                    }
                } else {
                    foreach ($PropertyToClear in $ClearProperty) {
                        '-metadata'
                        '' + $PropertyToClear + '=""'
                    }
                }

                if ($AlbumArt) {
                    # If we're setting album art, resolve the path
                    $raa =
                        if ([IO.File]::Exists($AlbumArt)) {
                            $AlbumArt
                        } else {
                            $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($AlbumArt) |
                                Get-item -literalPath { $_ } -Force |Select-Object -ExpandProperty Fullname
                        }

                    # Then we copy the image stream in
                    if (-not $raa) { continue }
                    $copyArgs = @('-i', $raa, '-c:a', 'copy', '-c:v','copy', '-map', '0:0','-map','1:0')
                    '-id3v2_version'
                    3
                    '-metadata:s:v'
                    'title="Album Cover"'  # indicate it's an album cover
                    '-metadata:s:v'
                    "comment=`"$AlbumArtType`"" # and then add a comment about what album art type it is.
                }
                if ($inputObject -is [Collections.IDictionary]) {
                    foreach ($kv in $inputObject.GetEnumerator()) {
                        '-metadata'
                        '' + $kv.Key + "=" + (
                            $kv.Value -replace '"','\"' -replace
                                '\[','(' -replace
                                '\]',')' -replace '&', 'and') + " "
                    }
                } elseif ($inputObject) {
                    foreach ($prop in $inputObject.psobject.properties) {
                        if ($inputObject -is [Xml.XmlElement] -and
                            $xmlPropertyNames -contains $prop.Name) { continue }
                        '-metadata'
                        '' + $prop.Name + '="' + ($prop.Value -replace '"','\"') + '"'
                    }
                }
            )

            if ($metadataArgs.Count) {
                if ($PSCmdlet.ShouldProcess("$inFile $metadataArgs")) {
                    & $ffmpeg -i $inFile @copyArgs @metadataArgs $tempFileName '-y' 2>&1 |
                        & { process {
                            if ('silentlycontinue', 'ignore' -notcontains $VerbosePreference) {
                                Write-Verbose "$_"
                            }
                        } }

                    if (Test-Path -LiteralPath $tempFileName) {
                        $newFile = Get-Item -LiteralPath $tempFileName
                        if ($newFile.Length) {
                            Move-Item -LiteralPath $tempFileName -Destination $inFile -Force
                        }
                    }
                }
            }

        }


        Write-Progress "Setting Metadata" " " -Completed -Id $id
    }
}