In my previous blog entry, Easily setting Tags on Media Files using PowerShell, I described how to set the media tags used by common MP3 players.
The reason I had this need stemed from a frustration from RIPing Audio Books into MP3s. For example, let's say I'm compressing a 10 CD book. Even with "good" audio naming from Internet Naming sources, you get 10 different albums with numberd tracks from 1 to whatever. Sometimes the naming fixes this and sometimes it does a miserable job. Copying and renaming the files doesn't do the job. When you RIP the CD the encoder creates Meta Tags Used by MP3 players.
When your favorite tool is a hammer...
I didn't even look for a tool to solve this problem so if there is one... that's not really the point. I was curious if I could solve it very easily using Powershell. This is what I want:
- Copy all files to a single directory
- Name all files with some base name with a Zero padded count.
- Provide for a flexible means for setting Tags on all Files. For example, Author
- Set the Album Tag so all files will appear as a single album
- Set Meta Track number to coorespond the the Count and Title to the Filename
When I RIP the CDs it easy to get them all under a single directory and organize the directories Alphabetically so the CDs are in order. The individually named where they also sort alphabetically. As such the following line will return all MP3s under a directory in the order they should be played:
dir -Recurse -Filter *.mp3
As such, I need a CmdLet where I can pipe these. This CmdLet will also take an Array parameter if that is your preference.
function Copy-NumberFile
( [system.IO.FileInfo] $file=$(Throw "File is required")
, [string] $BaseName=$(Throw "BaseName is required")
, $DestDir=$(Throw "DestDir is required")
, [int] $Count=$(Throw "Count is required")
, [int] $Digits=1
, [switch] $Verbose
, [switch] $PassThru
)
{
$nbrStr = "{0:d$Digits}" -f $Count
$NewName = "$($BaseName)$($nbrStr)$($file.Extension)"
$NewFileName = [System.IO.Path]::Combine($DestDir.FullName,$NewName)
if ($Verbose)
{
Write-Host "Copy $($file.fullname) to $NewFileName"
}
Copy-Item $file.FullName -Destination $NewFileName -PassThru:$PassThru
}
Function Organize-AudioBookFiles
( [System.IO.DirectoryInfo] $DestDir
, [hashtable] $Tags
, [string]$BaseName
, [int]$CountStart=1
, $Digits=-1
, [array]$Files )
{
begin
{
[array] $fileList = $null
function AddFile( $FileToCheck )
{
if ($fileToCheck -ne $null)
{
[system.IO.FileInfo] $file = $FileToCheck
if (!$file.Exists) { Throw "File does Not Exist" }
$file
}
}
if (!$DestDir.Exists)
{
$DestDir.Create()
}
if ((dir $DestDir.FullName | Measure-Object).Count -gt 0)
{
Throw "Destination Directory $($DestDir.FullName) is not empty"
}
foreach ($file in $files)
{
$fileList += AddFile($file)
}
}
process
{
$fileList += AddFile($_)
}
end
{
$Count = $CountStart
if ($digits -lt 0)
{
[int]$Digits = [System.Math]::Truncate(([System.Math]::Log10($fileList.Count + $Count)))+1
}
foreach ($file in $fileList)
{
$file = Copy-NumberFile -file $file -DestDir $DestDir -BaseName $BaseName `
-Digits $digits -Count ($Count) -Verbose -PassThru
$filename = $file.Name
$Tags.Track = $Count
$Tags.Title = $filename.Substring(0,($filename.length-$file.extension.length))
SetMedia-Tags -file $file -Tags $Tags
$Count++
}
}
}
I have a helper function "Copy-NumberFile". While I'm not sure if I'll ever need it in another operation, I can imagine it. It copies a file to a Destination Directory, renames it with a BaseName followed by a Number and preserving the extension. The number of Zero padded digits is specified in the $Digits parameter. The $PassThru allows the caller to get the FileInfo for the created file.
An example usage might look like:
$Tags = @{Artists="Book Author";Album="Book Title";Comment="This is a comment";Genres=@("Fiction")}
$files = dir -Recurse -Filter *.mp3
Organize-AudioBookFiles -Files $files -DestDir "C:\temp\Book" -BaseName "BookName " -Tags $Tags