Extracting iTunes Data and Responding to its Events using iTunes SDK and C++






4.64/5 (5 votes)
An article to show how iTunes SDK can be used to get all the songs and playlists of iTunes, getting Art work and getting the event notification from iTunes

Introduction
Around a year ago, I was writing a plug-in for one Media related application which extracts all the media items, meta-data and play lists from iTunes. The plug-in also responds to the events of iTunes like its database change due to addition/deletion of media item to/from iTunes. In this article, a very simple application is presented which loads all the data from iTunes by using the iTunes SDK and responds to its database change event by reloading all the data again. At that time not much resources were available on the internet dealing with the iTunes SDK, but I was not able to publish this article due to some non-technical issues. Some days back, I found that there are still not many resources available on CodeProject which deal with this iTunes SDK in C++. Most of the articles relating to iTunes SDK deal with C#, so I decided to write one article on this topic though it is very late.
The sample given here was originally written with iTunes version 5. I've tested it with the latest iTunes (ver 6.0.5.20) and it is working fine.
How to Use the Demo
You need to install iTunes, if it is not installed and add some media item to it. You'll get all the media items of iTunes in the sample and Artwork will be shown if the item is clicked, if any artwork is available. If you add/remove some item from the iTunes, the sample will also get updated.
Using the Code
All the media items added to iTunes come under Library tab. The loading of iTunes media item can be done in two ways. We can either load all the media items through Library or we can load it by play lists. In the sample, all data have been loaded through library. Here is the code snippet:
CoInitialize(NULL);
HRESULT hr;
CComPtr<IITUNES> spITunesApp;
// note - CLSID_iTunesApp and IID_IiTunes are defined in iTunesCOMInterface_i.c
//create iTunes component
hr = ::CoCreateInstance(CLSID_iTunesApp,
NULL, CLSCTX_LOCAL_SERVER,//local server
__uuidof(spITunesApp),
(void **)&spITunesApp);
if(FAILED(hr))
{
AfxMessageBox("Failed to load iTunes Component, is iTunes installed????");
return;
}
#if 1 //connection point to receive database change notification
IConnectionPointContainer * pCPC;
hr=spITunesApp->QueryInterface(IID_IConnectionPointContainer,(void **)&pCPC);
IConnectionPoint * pCP;
_IiTunesEvents * pITEvent;
hr=pCPC->FindConnectionPoint (__uuidof(pITEvent),&pCP);
pCPC->Release();
IUnknown *pSinkUnk;
CITunesEventSink * pSink;
pSink=new CITunesEventSink;
hr = pSink->QueryInterface (IID_IUnknown,(void **)&pSinkUnk);
DWORD dwAdvise;
hr = pCP->Advise(pSinkUnk,&dwAdvise);
#endif
CComPtr <IITSOURCECOLLECTION> spSourceCollection;
//check whether iTunes is already executing
//if it is not executing then we'll hide it
BOOL bRes=IsITunesNeedsToBeHidden();
HANDLE hThread;
//
//itunes pops up when we call
//the function of its component
//so hide it by this patchy method...
//
if(bRes) //iTunes was not there so we need to hide it
{
//create auto reset events
ghEvt[ThreadStarted]=CreateEvent(NULL,FALSE,FALSE,"ThreadStart");
ghEvt[ThreadEnded]=CreateEvent(NULL,FALSE,FALSE,"ThreadEnd");
//strat the thread to hide iTunes
hThread=(HANDLE)_beginthread(ThreadHideITunes,0,NULL);
//wait till the thread starts
WaitForSingleObject(ghEvt[ThreadStarted],INFINITE);
//get source collection
hr=spITunesApp->get_Sources(&spSourceCollection);
if(FAILED(hr))
{
return(ErrorMessage(hr));
}
//stop iTunes hide thread
SetEvent(ghEvt[ThreadEnded]);
//wait till the thread stops
WaitForSingleObject(hThread,INFINITE);
//Now minimize iTunes and Hide
ITunesMinimizeAndHide();
}
else //iTunes was there so need to hide do normal things
{
//get source collection
hr=spITunesApp->get_Sources(&spSourceCollection);
if(FAILED(hr) || hr==S_FALSE)
{
return(ErrorMessage(hr));
}
}
long countSource;
long countPlaylist;
long countTracklist;
//get the source count
hr=spSourceCollection->get_Count (&countSource);
CComPtr<IITSOURCE> spSource;
CComPtr<IITPLAYLISTCOLLECTION> spPlaylistCollection;
CComPtr<IITPLAYLIST> spPlaylist;
CComPtr<IITTRACKCOLLECTION> spTrackCollection;
CComPtr<IITARTWORK> spAlbumArt;
CComPtr<IITARTWORKCOLLECTION> spAlbumArtCollection;
ITArtworkFormat artFormat;
CComBSTR bstrName;
CComBSTR bstrLocation;
CComBSTR bstrArtist;
CComBSTR bstrAlbum;
CComBSTR bstrTime;
CComBSTR bstrYear;
CComBSTR bstrAlbumArtPath;
char szAlbumArtPath[255];
hr=spSourceCollection->get_Item (1,&spSource);
if(SUCCEEDED(hr))
{
spSource->get_Playlists (&spPlaylistCollection);
spPlaylistCollection->get_Count (&countPlaylist);
hr=spPlaylistCollection->get_Item (1,&spPlaylist);
spPlaylist->get_Tracks (&spTrackCollection);
spTrackCollection->get_Count (&countTracklist);
m_ListCtrl.DeleteAllItems ();
for(long k=1;k<=countTracklist;k++)
{
CComPtr<IITTRACK> spTrack;
hr=spTrackCollection->get_Item (k,&spTrack);
spTrack->get_Name (&bstrName);
CComPtr<IITFILEORCDTRACK> spFileTrack;
hr=spTrack->QueryInterface (__uuidof(spFileTrack),(void **)&spFileTrack);
//Get the ArtWork
hr=spTrack->get_Artwork (&spAlbumArtCollection);
hr=spAlbumArtCollection->get_Item (1,&spAlbumArt);
USES_CONVERSION;
if(SUCCEEDED(hr) && hr!=S_FALSE)
{
hr=spAlbumArt->get_Format (&artFormat);
if(SUCCEEDED(hr))
{
CString strTemp=OLE2T(bstrName);
strTemp.TrimLeft ("\"");
strTemp.TrimRight ("\"");
sprintf(szAlbumArtPath,
"%s%s%s%d%s",m_szCurDir,"\\",strTemp.GetBuffer (),k,".");
switch(artFormat)
{
case ITArtworkFormatJPEG:
strcat(szAlbumArtPath,"jpg");
bstrAlbumArtPath=T2OLE(szAlbumArtPath);
hr=spAlbumArt->SaveArtworkToFile (bstrAlbumArtPath);
break;
case ITArtworkFormatPNG :
strcat(szAlbumArtPath,"png");
bstrAlbumArtPath=T2OLE(szAlbumArtPath);
spAlbumArt->SaveArtworkToFile (bstrAlbumArtPath);
break;
case ITArtworkFormatBMP:
strcat(szAlbumArtPath,"bmp");
bstrAlbumArtPath=T2OLE(szAlbumArtPath);
spAlbumArt->SaveArtworkToFile (bstrAlbumArtPath);
break;
default:
break;
}
}
}
spFileTrack->get_Location (&bstrLocation);
spTrack->get_Artist (&bstrArtist);
spTrack->get_Album (&bstrAlbum);
spTrack->get_Time (&bstrTime);
// pTrack->get_Year (&bstrYear);
LV_ITEM item;
item.mask = LVIF_TEXT | LVIF_IMAGE;
item.iItem = m_ListCtrl.GetItemCount();
item.iImage = (k-1) % 5;
item.iSubItem = 0;
item.cchTextMax = 255;
item.pszText = OLE2T(bstrName);
m_ListCtrl.InsertItem (&item);
item.pszText = OLE2T(bstrLocation);
item.iSubItem = 1;
m_ListCtrl.SetItem(&item);
item.pszText = OLE2T(bstrArtist);
item.iSubItem = 2;
m_ListCtrl.SetItem(&item);
item.pszText = OLE2T(bstrAlbum);
item.iSubItem = 3;
m_ListCtrl.SetItem(&item);
item.pszText = OLE2T(bstrTime);
item.iSubItem = 4;
m_ListCtrl.SetItem(&item);
}
}
// AfxMessageBox("Now going to show Unsmart Playlist items....");
// UnsmartListItems(pITunes);
CoUninitialize();
The play list wise media item loading is also shown in the function UnsmartListItems(IiTunes *pITunes)
.
void UnsmartListItems(IiTunes *pITunes)
{
long lSourceID;
long lPlaylistID;
long lTrackId;
long countSource;
IITObject * pObject;
// IITUserPlaylist * pUserPlaylist;
IITSourceCollection * pCollection;
USES_CONVERSION;
short bIsSmart=0;
HRESULT hr=pITunes->get_Sources(&pCollection);
hr=pCollection->get_Count (&countSource);
IITSource * pSource;
IITPlaylistCollection * pPlaylistCollection;
IITPlaylist * pPlaylist;
IITTrackCollection * pTrackCollection;
IITTrack * pTrack;
CComPtr<IITFILEORCDTRACK> fileTrack;
long countPlaylist;
long countTracklist;
CComBSTR bstrName;
for(long i=1;i<=countSource;i++)
{
hr=pCollection->get_Item (i,&pSource);
//
pSource->get_SourceID (&lSourceID);
pSource->get_Playlists (&pPlaylistCollection);
pPlaylistCollection->get_Count (&countPlaylist);
for(long j=1;j<=countPlaylist;j++)
{
hr=pPlaylistCollection->get_Item (j,&pPlaylist);
pPlaylist->get_PlaylistID (&lPlaylistID);
hr=pITunes->GetITObjectByID (lSourceID,lPlaylistID,0,0,&pObject);
pObject->get_Name (&bstrName);
CComPtr<IITUSERPLAYLIST> spUserPlaylist;
hr=pPlaylist->QueryInterface(&spUserPlaylist);
if(SUCCEEDED(hr))
{
spUserPlaylist->get_Smart(&bIsSmart);
if(bIsSmart==0)
{
ITUserPlaylistSpecialKind plKind;
spUserPlaylist->get_SpecialKind (&plKind);
if(plKind==ITUserPlaylistSpecialKindPodcasts)
{
AfxMessageBox(OLE2T(bstrName));
pPlaylist->get_Tracks (&pTrackCollection);
pTrackCollection->get_Count (&countTracklist);
//m_ListCtrl.DeleteAllItems ();
for(long k=1;k<=countTracklist;k++)
{
hr=pTrackCollection->get_Item (k,&pTrack);
pTrack->get_TrackID(&lTrackId);
pTrack->get_Name (&bstrName);
AfxMessageBox(OLE2T(bstrName));
}
}
}
}
}
}
}
Points of Interest
In the sample, all the media items load code has been called from the main thread of the application. It should be put in a separate worker thread. It has just been put here for simplicity.
One pesky thing about the Tunes SDK is that whenever you call any of the iTunes component's methods, the iTunes UI will pop up. I couldn't find any way to block it, so in this sample iTunes has been hidden in a patchy way which results in a flicker in slow PCs.
Conclusion
I hope you guys find this sample helpful when you are going to interact with iTunes SDK. You can contact me, if you have anything to ask.
Credits/Acknowledgements
This sample uses code from two articles on CodeProject:
- Alternate row coloring CListCtrl by sdancer75, George Papaioannou
- WallpaperQ by Jason Henderson, for previewing album art in thumbnails
History
- 28th August, 2006: Initial post