Skip to content

set_MetaData

James Brundage edited this page Oct 23, 2026 · 1 revision

set_MetaData

Sets Wave Metadata

Sets Metadata about a Wave File.

Wave files can include metadata in a LIST chunk

Each piece of metadata is a 4-byte ASCII identifier, a 4-byte length, and an ASCII string.

Notes

Each header must start at a WORD boundary, or two bytes.

If the ASCII string had an odd number of characters, the next byte will be ignored.

Parameters

MetaData

required type position pipeline wildcard aliases
false IDictionary 1 false false
View Source
<#
.SYNOPSIS
    Sets Wave Metadata
.DESCRIPTION
    Sets Metadata about a Wave File.

    Wave files can include metadata in a LIST chunk

    Each piece of metadata is a 4-byte ASCII identifier,
    a 4-byte length, and an ASCII string.
.NOTES    
    Each header must start at a WORD boundary, or two bytes.
    
    If the ASCII string had an odd number of characters, the next byte will be ignored.
#>
param([Collections.IDictionary]$MetaData)

$shortNames = [Ordered]@{
    "ArchiveLocation" = 'IARL'
    "Artist" = 'IART'
    'BPM' = 'TBPM'
    'BeatsPerMinute' = 'TBPM'
    "Commissioned" = 'ICMS'
    "Comment" ='ICMT'
    "Comments" ='ICMT'
    "Copyright" = 'ICOP'
    "CreationDate"= 'ICRD'
    "Engineer"= 'IENG'
    "Genre"='IGNR'
    "Tag" = "IKEY"
    "Tags" = "IKEY"
    "Keyword"="IKEY"
    "Keywords"='IKEY'
    "Medium"='IMED'
    "Title"='INAM'
    'Album' = 'IPRD'
    "Product"= 'IPRD' 
    "TrackNumber"= 'IPRT'
    "Subject"= 'ISBJ'
    "Software" = 'ISFT'
    "Source"= 'ISRC'
    "SourceForm" =  'ISRF'
    "Technician" = 'ITCH'    
}

# https://mediaarea.net/BWFMetaEdit/listinfo

# Start off by refreshing out chunk list
$this | Add-Member NoteProperty '#Chunk' $null -Force
$chunks = $this.Chunk

# We will need to locate the list start if we have one
$listStart = 0 

# Walk over each chunk
$afterList = foreach ($chunk in $chunks) {
    # we will put the data into the first LIST chunk we see
    if ($chunk.id -eq 'LIST') {
        $listStart = $chunk.start
    }
    # and we will keep track of all chunks after this location
    elseif ($listStart) {
        $chunk
    }
}

# If we do not have a LIST
if (-not $listStart) {
    # Put it before the last chunk (often `data`)
    $listStart = $chunks[-1].start
    $afterList = @($chunks[-1])
}

# Create a writer
$writer = [IO.BinaryWriter]::new($this)
# and seek to the list start
$null = $this.Seek($listStart, 'Begin')
# Then write the list
$writer.Write([Text.Encoding]::ASCII.GetBytes("LIST"))

# Collect our list data
[byte[]]$listData = @(
    # `LIST` chunks begin with the ASCII characters `INFO`.
    [Text.Encoding]::ASCII.GetBytes("INFO")
    # followed by a series of key-value pairs
    foreach ($key in $MetaData.Keys) {
        # Each key can only be four ASCII characters.
        $shortKey = 
            # If the key was longer than four characters
            if ($shortNames[$key]) {
                $shortNames[$key]
            }
            elseif ("$key".Length -gt 4) {
                # take the first four characters
                "$key".Substring(0, 4)
            } else {
                # otherwise, pad right with spaces.
                "$key".PadRight(4, ' ')
            }
                    
        [Text.Encoding]::ASCII.GetBytes($shortKey)
        
        [byte[]]$valueBytes = if ($shortKey -match 'xml') {
            if ($MetaData[$key] -is [xml]) {
                [Text.Encoding]::UTF8.GetBytes(
                    $MetaData[$key].OuterXml
                )
            } else {
                [Text.Encoding]::UTF8.GetBytes("$($MetaData[$key])")
            }            
        } else {
            if ($MetaData[$key] -is [DateTime]) {
                [Text.Encoding]::ASCII.GetBytes("$(
                    ($MetaData[$key]).ToString('yyyy-MM-dd')
                )")
            } else {
                # Get the bytes stored in the value
                [Text.Encoding]::ASCII.GetBytes("$($MetaData[$key])")
            }
        }
        
        [BitConverter]::GetBytes([uint32]$valueBytes.Length)
        $valueBytes
        if ($valueBytes.Length % 2) {
            [byte]0
        }
    }
)
$writer.Write([uint32]$listData.Length)
$writer.Write($listData)

foreach ($after in $afterList) {
    $writer.Write([Text.Encoding]::ASCII.GetBytes($after.id))
    $writer.Write([uint32]$after.length)
    $writer.Write([byte[]]$after.data)
}

$null = $this.Seek(4, 'Begin')
# Then write our stream length, minus 8 bytes
$writer.Write([uint32]($this.Length - 8))

# and set our position back to the start.
$null = $this.Seek(0, 'Begin')

foreach ($prop in $this.psobject.properties) {
    if ($prop.Name -match '^#') {
        $prop.Value = $null
    }
}

return $this

Clone this wiki locally