Reading books is one of my favorite past times, but since I have 4 kids and an insane job… I settle for listening to them as audiobooks. I wanted to rip some Audiobook CD’s to WMA for playing on my Sansa Fuze and I wanted to tinker with automating Windows Media Encoder. Combine all that with some perverse pleasure in doing almost anything in powershell and you have the following script.
I couldn’t find a .NET class for ripping audio from CDs, but I did find BonkEnc that has a command line executable which I could conveniently automate from powershell. You should download the plain zip package of BonkEnc from http://www.bonkenc.org and extract the contents to a subfolder of your Module Root.
Using BonkEnc to end up with a folder full of WAV files from the CD was relatively easy. Trying to script the Windows Media Encoder was an exercise in pure brute force. I took every vbscript example I could find and attempted to port to Powershell, which I finally managed; but it may not be entirely pretty. You be the judge.
I’ve been using this script as a dot-sourced PS1 for a few weeks now, but due to the dependency on 2 third party components (bonkenc and taglib-sharp) – finding a way to make it portable for posting online was frustrating me. I had too many hard coded paths. So I used this as an excuse to play with Modules in CTP2. As such the script presented does require CTP2.
You’ll need to download Taglib-Sharp from http://www.taglib-sharp.com/Download/ and place the taglib-sharp.dll in your Module Root folder or a subfolder.
And finally, be sure you download and install the 32bit version of Windows Media Encoder from http://www.microsoft.com/downloads/details.aspx?FamilyID=5691ba02-e496-465a-bba9-b2f1182cdf24&displaylang=en. If you are using Vista, be sure to visit http://support.microsoft.com/default.aspx/kb/929182 for a hotfix.
Create a folder named “Packages” under your “WindowsPowershell” folder (in your “Documents” or “My Documents” folder). Next create a folder named “Rip-Audiobook” under the newly created Packages folder and drop the Rip-Audiobook.psm1 file into it. This is your Module Root folder. Drop BonkEnc and Taglib-sharp in this folder (or subfolder) and execute the following line in powershell:
45# add-module Rip-Audiobook
Insert the Audiobook CD and execute the following:
48# Rip-Audiobook
Follow the prompts and enter requested information so the resultant WMA file will be properly Tagged. Tag information is saved in an XML file for the next time you run Rip-Audiobook. You can just press ENTER to avoid reentering information that hasn’t changed.
Answer the following configuration questions (Pressing ENTER will use the default value shown).
Destination Folder (C:\Users\gaurhoth\music):
Title (The Fires of Heaven):
Author (Robert Jordan):
Series (Wheel of Time, Book 05):
CD Number (2):
You’ll be prompted when everything is completed and you should find your WMA file where ever you specified for the ‘Destination Folder’.
Here’s the Code (There’s a link to download the PSM1 file at the bottom of this post).
function Start-Process {
# Allows us to start the process with -elevated switch
# to accomodate Vista. You'll be prompted for elevation
# if you don't run powershell elevated.
param ($apppath, [switch]$elevated)
$se = new-object "System.Diagnostics.ProcessStartInfo"
$se.UseShellExecute = $FALSE
if ($elevated) { $se.Verb = "runas" }
$se.RedirectStandardOutput = $TRUE
$se.FileName = $apppath
$se.Arguments = [string]$Args
[System.Diagnostics.Process]::Start($se)
}
function New-TempFolder {
# Prepare new temporary folder
do {
$temp = [io.path]::GetTempPath() + [io.path]::GetRandomFileName()
} while ( $temp | Test-Path )
mkdir $temp
}
function Encode-Audio {
param (
[string]$destination,
[IO.FileInfo[]]$SourceFiles,
$Profile = 'Windows Media Audio 8 for Dial-up Modem (CD quality, 64 Kbps)'
)
Write-Host @'
______________________________________________________________
/ Encode-Audio:
| by Gaurhoth (http://www.skyeagle.net/blog)
|
| This script encodes a set of source WAV files to a single WMA.
|
| Example Usage:
| Encode-Audio -destination "$home\Music\Encoded-Audio.WMA" `
| -SourceFiles $("$home\music\WAVDIR\" | dir | sort)
|
| Requirements:
| Windows Media Encoder 9 (32bit tested)
| http://www.microsoft.com/downloads/details.aspx?FamilyID=5691ba02-e496-465a-bba9-b2f1182cdf24&displaylang=en
\______________________________________________________________
'@
$encoder = new-object -com WMEncEng.WMEncoder
$sc = $encoder.SourceGroupCollection
$encoder.file.localfilename = $destination
$profile = $encoder.profilecollection | ? { $_.name -eq $profile }
# cycle through all WAV files and add a 'sourcegroup' for
# each and assign correct encode profile.
$sourcefiles = $sourcefiles | ? { $_.Extension -eq '.wav' }
for ($i = 0; $i -lt ($sourcefiles.count); $i ++ ) {
$sg = $sc.add($sourcefiles[$i].basename)
$audsrc = $sg.addSource(1)
$audsrc.setinput($sourcefiles[$i].fullname)
$sg.profile = $profile
# Set rollover to move to next Sourcegroup unless this is last file
if ($i -lt ($sourcefiles.count - 1 )) { $sg.SetAutoRollover([int](-1),[string]$($sourcefiles[$i + 1].basename)) }
}
$sc.Active = $sc.Item(0)
$encoder.autostop = $TRUE
$encoder.PrepareToEncode($TRUE)
$encoder.start()
$totalwavesize = ($sourcefiles | Measure-Object length -Sum).sum
do { $per = [int]((($destination | dir).length) / ($totalwavesize / 21) * 100)
Write-Progress -Activity "Encoding Audio" -Status "progress" -PercentComplete $per
start-sleep -milli 500
} until ( $encoder.runstate -eq 5 )
write-progress -Activity "Encoding Audio" -Status "progress" -completed
$encoder.preparetoencode($FALSE)
$encoder.SourceGroupCollection | % { $encoder.sourcegroupcollection.Remove($_.name) }
$encoder.stop()
$encoder = $null
}
function Rip-Audiobook {
Write-Host @'
______________________________________________________________
/ Rip-AudioBook:
| by Gaurhoth (http://www.skyeagle.net/blog)
|
| This script takes an Audio CD and rips each track to
| a set of WAVE files in a temporary folder. These WAVE
| files are then processed using Windows Media Encoder
| to a single merged WMA file that is saved in folder
| specified by $DestinationPath.
|
| Requirements:
| BonkEnc 1.07 | http://www.bonkenc.org/
| Taglib-Sharp
| http://www.taglib-sharp.com/Download/
| Windows Media Encoder 9 (32bit tested)
| http://www.microsoft.com/downloads/details.aspx?FamilyID=5691ba02-e496-465a-bba9-b2f1182cdf24&displaylang=en
\______________________________________________________________
'@
$configfile="$PsScriptRoot\psbonkconfig.xml"
$cddrive = 0 # change to reflect a different CD Rom drive.
# Pull configuration information from xml file
if (Test-Path $configfile) {
$config = Import-Clixml $configfile
} else {
# No config file. Start from scratch.
$config = "" | Select Title,Author,Series,cd,bonkexe,taglib,destinationpath
}
dir $PsScriptRoot -Filter "taglib-sharp.dll" -Recurse | % {
$config.taglib = $_.fullname
[void][Reflection.Assembly]::LoadFile($config.taglib)
}
Write-Host "Answer the following configuration questions (Pressing ENTER will use the default value shown)." -ForegroundColor Yellow
# Try to find becmd.exe (bonkenc cmd line ripper) somewhere in the Module Root or subfolder.
dir $PsScriptRoot -Filter "becmd.exe" -Recurse | % {
$config.bonkexe = $_.fullname
}
while (-not ([io.file]::exists($($config.bonkexe)))) {
Write-Host -foreground yellow "`nbecmd.exe path was not found. Please enter full path to becmd.exe."
Read-Host "Enter path to becmd.exe ($($config.bonkexe))" | % { if ($_) { $config.bonkexe = $_ } }
}
if (-not $config.destinationpath) { $config.destinationpath = "$home\music" }
Read-Host "Destination Folder `($($config.destinationpath)`)" | % { if ($_) { $config.destinationpath = $_ } }
while (-not ([io.directory]::Exists($config.destinationpath))) {
Write-Host "`nThe folder specified ($($config.destinationpath)) does not exists. Please specify a valid folder."
Read-Host "Destination Folder `($($config.destinationpath)`)" | % { if ($_) { $config.destinationpath = $_ } }
}
# Ask for tag information using defaults from $config object
# Pressing Enter accepts default value.
Read-Host "Title `($($config.title)`)" | % { if ($_) { $config.Title = $_ } }
Read-Host "Author `($($config.Author)`)" | % { if ($_) { $config.Author = $_ } }
Read-Host "Series `($($config.Series)`)" | % { if ($_) { $config.Series = $_ } }
Read-Host "CD Number `($($config.CD)`)" | % { if ($_) { $config.CD = $_ } }
$temp = New-Tempfolder
$bonksession = Start-Process -elevated $config.bonkexe "-cd $cddrive -e WAVE -t 120 -d $temp -track all"
write-host -foreground Yellow "`nPlease Wait... running BonkEnc to extract audio from your CD."
$bonksession.waitforexit()
(New-Object -ComObject WMPlayer.ocx).cdromCollection.Item($cddrive).eject()
$destination = "{0}\{1} - {2}.wma" -f $config.destinationpath,$config.CD,$config.Title
write-host -foreground Yellow "`nRunning Windows Media Encoder to convert the extracted audio to WMA..."
if ( $temp | dir ) {
Encode-Audio -destination $destination -sourcefiles $($temp | dir | sort)
# Tag the WMA file
$media = [TagLib.File]::Create($destination)
$media.Tag.Artists = $config.author
$media.tag.Album = $config.series
$media.Tag.Title = $config.title
$media.Tag.Track = $config.cd
$media.Save()
# Increment the track/cd number so it'll default to
# correct number on next run.
$config.cd = $([int]$config.cd + 1)
$config | Export-Clixml $configfile
Write-Host -Foreground Yellow "`n`nCompleted. CD has been saved to $destination."
} else {
write-host "Failed to find CD Tracks to encode. Be sure to insert a CD and close the Tray."
}
del "$temp\cd*track*.wav"
del $temp -rec
}
Export-ModuleMember Rip-Audiobook
Download Link: rip-audiobook.psm1 (6.98 kb)