I was installing the latest version of the Zune software and actually read a bit of the license agreement. This line caught my eye:
Distributable Code. The software contains code that you are permitted to distribute in programs you develop if you comply with the terms below.
Oh, really? Programs that I develop? This reinforced a suspicion I had that there was a usable API in the Zune software. And there is. It is ZunedbAPI.dll. I don’t know much about it, but I do know how to read the Zune collection (formerly known as the library). It's as simple as adding a reference to that DLL, found in the Zune program folder. A pre-caveat: There are dependancies with this DLL, so you can either run your custom app in the Zune folder, or you can use trial and error to find out which DLLs are needed. I chose the former out of convenience.
So what are some of the objects you’re going to be dealing with to read the library? There’s the MicrosoftZuneLibrary namespace and in it are some objects you will learn to know:
- ZuneLibrary
- ZuneQueryList
- ZuneQueryItem
ZuneLibrary is the start of everything. But just creating an instance of it will not do anything. You also need to call the Initialize method on it:
Dim zuneLib As ZuneLibrary
zuneLib = New ZuneLibrary
zuneLib.Initialize()
And you MUST clean up after yourself. These objects are very sensitive to being left hanging and when the GC comes along, it’s going to throw up all over your program. So always call Dispose when you are done with it. Never just let it fall out of scope.
So you now have a connection to the Zune collection. The queries are a little bit limiting; there’s nothing free-form about them, but you can use them to get the raw data and work it from there. There are methods like:
- QueryDatabase
- GetAlbumsByArtists
- GetTracksByAlbum
- GetTracksByPlaylist
And others for you to discover. These methods take some parameters that might need explaining. Once one is explained, you should be able to apply that logic to the others. Let’s look at the first one: QueryDatabase. Its signature is:
Public Function QueryDatabase(ByVal QueryType As EQueryType,
ByVal LibraryView As Integer, ByVal SortType As EQuerySortType,
ByVal SortAtom As UInteger,
ByVal propertyBag As MicrosoftZuneInterop.QueryPropertyBag)
As MicrosoftZuneLibrary.ZuneQueryList
The first parameter is an enum. That makes it easy to pick a query type. Look at some of the choices:
- EQueryType.eQueryTypeAllAlbums
- EQueryType.eQueryTypeAllPhotos
- EQueryType.eQueryTypeAllTracks
I’m kind of sticking to these, because the others probably have parameters taht need to be sent in a PropertyBag and I haven’t worked them out yet. So the easy example is to list all albums. Pick the proper enum and continue.
The next parameter is the library view. I’ve not found this to work with anything but zero. Then in the next parameter you specify the sort type. It’s an enum and doesn’t need explanation.
The next to last parameter refers to an “atom”. This is a Uint value for a column. There’s no indication of this, but the atoms are listed in the enum: SchemaMap. The difficult thing is figuring out which atoms apply to which file types. I have a bit of code at the end of this post to help with that.
So you need to choose which column you want to sort by by using the value from the SchemaMap enum. If you pick an atom that isn’t valid, your call will error out. It’s pretty rough. You can always specify zero and it doesn’t seem to care.
Last property is the PropertyBag. This is important for some queries for sure, but I haven’t taken the time to figure out how to instantiate one and put the values into it. So for now, using Nothing is the proper value.
The query will come back into a ZuneQueryList object. Or will be Nothing if the query failed. Like if you set an invalid library view value.
What’s this look like in code? Here’s a query for all artists:
Dim artists As MicrosoftZuneLibrary.ZuneQueryList
artists = zuneLib.QueryDatabase(EQueryType.eQueryTypeAllAlbumArtists,
0, EQuerySortType.eQuerySortOrderAscending,
CUInt(MicrosoftZuneLibrary.SchemaMap.kiIndex_DisplayArtist), Nothing)
Not bad. Now, how to get at the stuff inside the QueryList? It doesn’t have any Item property. It does have a Count property, so you could loop through it, but how do you get the fields?
The answer is the GetFieldValue function. Let’s see one of its signatures (it’s overloaded):
Public Function GetFieldValue(ByVal index As UInteger,
ByVal type As System.Type, ByVal Atom As UInteger) As Object
Seems pretty straightforward, the Index is the index of the item you want, the type is the datatype you expect, and the atom is, well, an atom. Using the SchemaMap enumeration, this is easy.
There is another way to get an item, and that is using the ZuneQueryItem object. You instantiate it using a query list and an index. Once you have a ZuneQueryItem, you can use the GetFieldValue function on it, which has a similar syntax, obviously missing the index parameter.
Let’s see that in code. I have a listbox called lstArtists and a custom class that holds the ID and name of the object (overriding the ToString method to display the name):
For i As Integer = 0 To artists.Count - 1
lstArtists.Items.Add(CStr(artists.GetFieldValue(CUInt(i),
GetType(String),
CUInt(MicrosoftZuneLibrary.SchemaMap.kiIndex_DisplayArtist), ""))
Next
Or maybe the other way, when you select an item:
Dim i As MicrosoftZuneLibrary.ZuneQueryItem
i = New MicrosoftZuneLibrary.ZuneQueryItem(artists, lstArtists.SelectedIndex)
That’s it. You can now query the Zune collection! Always be sure to clean up after yourself.
Now, here’s that code that I said is helpful for finding out the atoms on an object. Create a form with a DataGridView and add this method.
Set up your main code to perform a query and get a ZuneQueryItem from the results. Then create the display form and pass that QueryItem to this method.
The method:
Public Sub ShowItem(ByVal i As MicrosoftZuneLibrary.ZuneQueryItem)
Dim val As String
For Each v As MicrosoftZuneLibrary.SchemaMap In
[Enum].GetValues(GetType(MicrosoftZuneLibrary.SchemaMap))
val = CStr(i.GetFieldValue(GetType(String), CUInt(v)))
If val Is Nothing Then val = CStr(i.GetFieldValue(GetType(Date), CUInt(v)))
If val Is Nothing Then val = CStr(i.GetFieldValue(GetType(Integer), CUInt(v)))
If val Is Nothing Then val = CStr(i.GetFieldValue(GetType(Guid), CUInt(v)))
dgFile.Rows.Add([Enum].GetName(GetType(MicrosoftZuneLibrary.SchemaMap), v), val)
Next
Me.Show()
End Sub
If anyone finds out anything further about the Zune API, I am found on ZuneBoards. Look me up there.