Using Powershell to play MP3 Audiobooks on Windows Phone

Re-post from here

 

So about a year ago I changed my phone to a Windows mobile 7. Overall I’m really happy with it. Without setting the world alight with evangelistic wars, I think that if the Metro interface had come before the iPhone arrived then it’s likely it would be the dominant UI.

Anyway, one major problem I had arrived when it came to playing audiobooks. The issue was that I had three challenges when playing on the windows phone.

a) For space considerations, I wanted “read” chapters to be removed from the phone automatically

b) Some books have hundreds of chapters, but some have 12 chapters each >1hr in length. It was almost impossible to pick up where you left off. Therefore I needed to use the podcast “resume” functionality in Zune

c) This led to problem #3 – when I converted the “genre” using MP3Tagedit to podcast, they would frequently end up in a jumbled play-order depending on their date, so I needed to then ensure that Chapter1 had an earlier timestamp than chapter 2 etc. to force the right order in WP7. I could eventually get what I wanted but it was very painful and involved a bunch of manual steps as well as using BulkEditor and MP3Tagedit.

I found part of the answer in an excellent post over here, where it referenced TAGLIB# . So I launched powershell for the first time and wrote the script below.

Now, if you target it at a folder such as…

William Shakespeare/

Merchant of Venice/

Chapter1.mp3 ……

Hamlet/

Chapter1.mp3…..

It will loop through the subdirectories… renaming the files in sequence… setting the appropriate MP3 tag entries… Author = William Shakespeare/Album = BookDirectoryName/Chapter = Title.. and setting timestamp to force Zune to recognise chapter 1 as being before chapter 2..

So, here’s the code I used:

Function Zune
{
	[CmdletBinding()]
	param(
		[Parameter(Position=0, Mandatory=$True)]
		[ValidateNotNullOrEmpty()]
		[System.String]
		$Library,

		[Parameter(Position=1)]
		[ValidateNotNull()]
		[System.String]
		$Genre
	)

	cls

	$OriginDir = Get-Location

	#========Does the libary exist and contain books?=============================================

	if ((Test-Path -Path $Library) -eq $false) {
		write-output "$Library Does Not Exist"
		Break
	}

	$Books = Get-ChildItem $Library | where {$_.psiscontainer}
	if (!$books) {
		Write-Output "$Library Contains no books"
		Break
	}

	$librarylocation = Get-Item $Library

	fault Parameters===========================================================
	if (!$Genre)
	{
		$Genre="Podcast"
		Write-Host "Genre is blank, using Default $Genre" -foregroundcolor DarkGreen
	}
	else
	{
		Write-Output "Genre is " + $Genre
	}

	#========Load Assemblies======================================================================
	$TaglibLocation = (Resolve-path ($ProfileDir + "taglibtaglib-sharp.dll"))
	Add-Type -Path ($TaglibLocation)
	Write-output "Taglib Loaded Successfully"

	#========Set up Recursive Subdirectories======================================================
	Write-Output "Processing the Library $Library"
	$NumberBooks = $books | Measure-Object | Select-Object -ExpandProperty Count
	Write-Output "Total Number of Runs is $Numberbooks"

	if ($NumberBooks -gt 0)
	{
		Set-Location $Librarylocation
		foreach ($book in $Books)
		{
			Write-Host "==================================================" -foregroundcolor Blue
			Write-Output "$("Processing the Book ")$($book.name)$(" in ")$($librarylocation)"
			$Chapters = Get-ChildItem $book.name *.mp3 | Sort-Object Name
			$NumberChapters = $Chapters | Measure-Object | Select-Object -ExpandProperty Count

			if ($NumberChapters -gt 0)
			{

				if ($NumberChapters -le 100)
				{
					$pad = 2
				}
				elseif ($NumberChapters -le 255)
				{
					$pad = 3
				}
				elseif ($NumberChapters -ge 256)
				{
					Write-Output "$("Too Many chapters in the book :")$($book.name)"
					Break
				}

				Write-Output "$("Processing ")$($NumberChapters)$(" Chapters")"

				#========Set up Recursive Subdirectories==========================================
				$book.fullname
				set-location $book.fullname
				$i=1
				foreach ($chapter in $Chapters)
				{
					$title = "{0:D$pad}" -f ($i) +"$(" of $NumberChapters- ")$($book.name)"
					$Filename = $title + '.mp3'
					$chapter.fullname
					$media = [TagLib.File]::Create($chapter.Fullname)
					$media.Tag.Title = $title
					$media.Tag.Performers = "Narrator"
					$media.Tag.AlbumArtists = (get-culture).Textinfo.Totitlecase($Library.tolower())
					$media.Tag.Composers = "Composer"
					$media.Tag.Album = $book.name
					$media.Tag.Comment = $chapter.Name
					$media.Tag.Genres = $Genre

					if (!$media.Tag.Year) {$media.tag.year = (Get-date).Year}
					$media.Tag.Track = $i
					$media.Tag.Artists = (get-culture).Textinfo.Totitlecase($Library.tolower())

					$media.Save()

					$chapter.Creationtime = (Get-Date).date.addminutes($i)
					$chapter.LastWriteTime = (Get-Date).date.addminutes($i)
					#$chapter.LastAccessTime = (Get-Date).date.addminutes($i)
					$chapter.LastWriteTimeUTC = (Get-Date).date.addminutes($i)
					$chapter.CreationtimeUTC = (Get-Date).date.addminutes($i)
					#$chapter.LastAccessTimeUTC = (Get-Date).date.addminutes($i)
					Write-Output "$("Renaming ")$($chapter.name)$(" to ")$($Filename)"
					Rename-Item $chapter.fullname $filename

					$i++
				}
			}
			else
			{
				Write-Output "$("No Chapters inside the book :")$($book)"
			}

		Set-Location $Librarylocation
		}

		#===============================================================================================
		#========================     end of loop on books =============================================
		#===============================================================================================
	}
	else
	{
		Write-Output "$($"No Books inside the Library "$($Library))"
	}

	#===================================================================================================
	#========================     end of function     ==================================================
	#===================================================================================================
	Set-Location $OriginDir
}

Export-ModuleMember -Function Zune