Summary
This update addresses a metadata encoding issue which causes Free Lossless Audio Codec (FLAC) music files to become unplayable if their title, artist, or other metadata is changed.Â
Cause
This issue might occur when the FLAC files contain an ID3 frame before the FLAC header. The ID3 frame contains metadata such as title and artist. The FLAC property handler assumed that all FLAC files started with the 4 byte start code fLaC and did not take into account the ID3 frame at the beginning of the file. Therefore, the ID3 frame would be overwritten without the start code fLaC rendering the file unplayable.
Resolution
To prevent this issue for future FLAC music files, install May 25, 2021—KB5003214 (OS Builds 19041.1013, 19042.1013, and 19043.1013) Preview.
To repair affected FLAC music files, run the following PowerShell script.
Important:Â This script will not restore the lost metadata that was stored in the ID3 frame. However, it does make the file playable again.
-
Open Notepad.
-
Copy-and-paste the following script into notepad:
# Copyright 2021 Microsoft
# This script will repair a FLAC file that has been corrupted by Media Foundation in reference to KB5003430.
# Refer to KB5003430 for further information
param(
[parameter(Mandatory=$true,
HelpMessage="The path to the FLAC file that has been corrupted by Media Foundation",
ValueFromRemainingArguments=$true)]
[ValidateScript({ -not [String]::IsNullOrEmpty($_) -and (Test-Path $_) })]
[String]$File
)
# We need to back up the current file incase we have any errors
$FileDirectory = Split-Path -Resolve $File
$Filename = Split-Path -Leaf -Resolve $File
$FullPath = Join-Path -Resolve $FileDirectory $Filename
$Filename = [String]::Format("Backup_{0:yyyyMMdd_hhmmss}_{1}", [DateTime]::Now, $Filename)
$BackupLocation = Join-Path $FileDirectory $Filename
Write-Output "Microsoft FLAC Repair Tool. This tool will repair a FLAC audio file that was corrupted when editing its details."
Write-Output "Affected File: $FullPath"
Write-Output "A backup of the file will be made: $BackupLocation"
Write-Output "Do you wish to continue?"
$choice=$host.ui.PromptForChoice("Fixing FLAC Script", "Do you wish to continue", ('&Yes', '&No'), 1)
function ParseStreamInfoMetadataBlock([System.IO.FileStream]$stream)
{
$blockType = $stream.ReadByte()
$lastBlock = ($blockType -shr 7) -ne 0
$blockType = $blockType -band 0x7F
if ($blockType -ne 0)
{
return $false
}
$blockSize = (($stream.ReadByte() -shl 16) -bor ($stream.ReadByte() -shl 8) -bor $stream.ReadByte())
if ($blockSize -lt 34)
{
return $false
}
$minAudioBlockSize = ($stream.ReadByte() -shl 8) -bor $stream.ReadByte()
$maxAudioBlockSize = ($stream.ReadByte() -shl 8) -bor $stream.ReadByte()
if ($minAudioBlockSize -lt 16 -or $maxAudioBlockSize -lt 16)
{
return $false
}
$minFrameSize = (($stream.ReadByte() -shl 16) -bor ($stream.ReadByte() -shl 8) -bor $stream.ReadByte())
$maxFrameSize = (($stream.ReadByte() -shl 16) -bor ($stream.ReadByte() -shl 8) -bor $stream.ReadByte())
$sampleInfo = (($stream.ReadByte() -shl 24) -bor ($stream.ReadByte() -shl 16) -bor ($stream.ReadByte() -shl 8) -bor $stream.ReadByte())
$sampleRate = $sampleInfo -shr 12
$channelCount = (($sampleInfo -shr 9) -band 0x7) + 1
$bitsPerSample = (($sampleInfo -shr 4) -band 0x1F) + 1
[UInt64]$sampleCount = (($stream.ReadByte() -shl 24) -bor ($stream.ReadByte() -shl 16) -bor ($stream.ReadByte() -shl 8) -bor $stream.ReadByte())
$sampleCount = (([UInt64]$sampleInfo -band 0xF) -shl 32) -bor $sampleCount
$MD5HashBytes = New-Object byte[] 16
$stream.Read($MD5HashBytes, 0, $MD5HashBytes.Length)
$MD5Hash = [Guid]($MD5HashBytes)
if ($sampleRate -eq 0)
{
return $false
}
# Passing these checks means that we likely have a stream info header and can rebuild the file
Write-Output "File Stream Information"
Write-Output "Sample Rate: $sampleRate"
Write-Output "Audio Channels: $channelCount"
Write-Output "Sample Depth: $bitsPerSample"
Write-Output "MD5 Audio Sample Hash: $MD5Hash"
return $true
}
if ($choice -eq 0)
{
Copy-Item $FullPath -Destination $BackupLocation -Force
$stream = [System.IO.File]::Open($FullPath, [System.IO.FileMode]::Open)
$stream.Seek(4, [System.IO.SeekOrigin]::Begin)
while ($stream.ReadByte() -eq 0) {}
# We now need to figure out where a valid FLAC metadata frame begins
# We are likely pointing to the last byte of the size member so we'll seek back 4 bytes and retry
$flacDataStartPosition = $stream.Position - 4
$stream.Seek($flacDataStartPosition, [System.IO.SeekOrigin]::Begin)
while (-not(ParseStreamInfoMetadataBlock($stream)))
{
$flacDataStartPosition = $flacDataStartPosition + 1
$stream.Seek($flacDataStartPosition, [System.IO.SeekOrigin]::Begin)
}
# Insert the start code
$stream.Seek($flacDataStartPosition, [System.IO.SeekOrigin]::Begin)
if (Test-Path "$FullPath.tmp")
{
Remove-Item "$FullPath.tmp"
}
$fixedStream = [System.IO.File]::Open("$FullPath.tmp", [System.IO.FileMode]::CreateNew)
[byte[]]$startCode = [char[]]('f', 'L', 'a', 'C');
$fixedStream.Write($startCode, 0, $startCode.Length)
$stream.CopyTo($fixedStream)
$stream.Close()
$fixedStream.Close()
Move-Item -Force "$FullPath.tmp" $FullPath
}
-
On the File menu, click Save.
-
In the Save As dialog box, locate the folder in which you want to save the PowerShell script.
-
In the File name box, type FixFlacFiles.ps1, change the Save as type box to Text Documents (*.txt) and then click Save.
-
In Windows Explorer, locate the PowerShell script you saved.
-
Right-click the script and then click Run with PowerShell.
-
When prompted, type in the file name of the unplayable FLAC file and then press Enter.