Click here to Skip to main content
15,880,469 members
Articles / Programming Languages / C#

LyricsFetcher - The Easiest Way to Find Lyrics for your Songs

Rate me:
Please Sign up or sign in to vote.
4.93/5 (82 votes)
29 Oct 2009GPL325 min read 201.1K   2.4K   184  
An article describing the development of a non-trivial C#/.NET application to fetch lyrics for songs.
/*
 * This file holds the class implementations necessary to interact with iTunes.
 *
 * Author: Phillip Piper
 * Date: 8/01/2008 4:28 PM
 *
 * CHANGE LOG:
 * 2009-02-28 JPP  - Load KindsToIgnore from Settings
 *                 - Use of Fast loader is now configurable
 * 2009-02-15 JPP  Filter out non-song kinds in SongLoader
 * 2008-01-08 JPP  Initial Version
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Xml.XPath;
using iTunesLib;

namespace LyricsFetcher
{
    /// <summary>
    /// The class implements a SongLibrary based on the iTunes music library
    /// </summary>
    public class ITunesLibrary : SongLibrary
    {
        protected override SongLoader ChooseSongLoader() {
            if (Properties.Settings.Default.UseFastITunesLoader)
                return new FastITunesSongLoader();
            else
                return new ITunesSongLoader();
        }

        public override bool IsPlaying(Song song) {
            ITunesSong itSong = song as ITunesSong;
            if (itSong != null && itSong.Track != null)
                return ITunes.Instance.IsTrackPlaying(itSong.Track);
            else
                return false;
        }

        public override void Play(Song song) {
            ITunesSong itSong = song as ITunesSong;
            if (itSong != null && itSong.Track != null)
                itSong.Track.Play();
        }

        public override void StopPlaying() {
            ITunes.Instance.Stop();
        }

        #region Event handlers

        public override void InitializeEvents() {
            ITunes.Instance.Application.OnPlayerPlayEvent += new _IiTunesEvents_OnPlayerPlayEventEventHandler(ITunes_OnPlayerPlayEvent);
            ITunes.Instance.Application.OnPlayerPlayingTrackChangedEvent += new _IiTunesEvents_OnPlayerPlayingTrackChangedEventEventHandler(ITunes_OnPlayerPlayingTrackChangedEvent);
            ITunes.Instance.Application.OnPlayerStopEvent += new _IiTunesEvents_OnPlayerStopEventEventHandler(ITunes_OnPlayerStopEvent);
            ITunes.Instance.Application.OnQuittingEvent += new _IiTunesEvents_OnQuittingEventEventHandler(ITunes_OnQuittingEvent);
        }

        public override void DeinitializeEvents() {
            ITunes.Instance.Application.OnPlayerPlayEvent -= new _IiTunesEvents_OnPlayerPlayEventEventHandler(ITunes_OnPlayerPlayEvent);
            ITunes.Instance.Application.OnPlayerPlayingTrackChangedEvent -= new _IiTunesEvents_OnPlayerPlayingTrackChangedEventEventHandler(ITunes_OnPlayerPlayingTrackChangedEvent);
            ITunes.Instance.Application.OnPlayerStopEvent -= new _IiTunesEvents_OnPlayerStopEventEventHandler(ITunes_OnPlayerStopEvent);
            ITunes.Instance.Application.OnQuittingEvent -= new _IiTunesEvents_OnQuittingEventEventHandler(ITunes_OnQuittingEvent);
        }

        void ITunes_OnPlayerStopEvent(object iTrack) {
            this.OnPlayEvent(new EventArgs());
        }

        void ITunes_OnPlayerPlayingTrackChangedEvent(object iTrack) {
            this.OnPlayEvent(new EventArgs());
        }

        void ITunes_OnPlayerPlayEvent(object iTrack) {
            this.OnPlayEvent(new EventArgs());
        }

        void ITunes_OnQuittingEvent() {
            this.OnQuitEvent(new EventArgs());
            ITunes.Instance.Release();
        }

        #endregion
    }

    /// <summary>
    /// This class implements a loader that loads tracks from the iTunes library using
    /// the iTunes COM interface.
    /// </summary>
    public class ITunesSongLoader : SongLoader
    {
        /// <summary>
        /// Do the actual work of loading the lyrics from the library
        /// </summary>
        /// <param name="e"></param>
        /// <returns>Ignored</returns>
        protected override object DoWork(DoWorkEventArgs e) {
            try {
                IITTrackCollection tracks = ITunes.Instance.AllTracks;

                // How many tracks are there and how many songs should we fetch?
                int trackCount = tracks.Count;
                int maxSongs = trackCount;
                if (this.MaxSongsToFetch > 0)
                    maxSongs = Math.Min(trackCount, this.MaxSongsToFetch);

                this.ReportProgress(0, "Gettings songs...");
                for (int i = 1; i <= trackCount && this.Songs.Count < maxSongs && this.CanContinueRunning; i++) {

                    IITTrack track = tracks[i];
                    if (track.Kind == ITTrackKind.ITTrackKindFile)
                        if (!this.KindsToIgnore.Contains(track.KindAsString))
                            this.AddSong(new ITunesSong(track));

                    this.ReportProgress((i * 100) / maxSongs);
                }
            }
            catch (COMException ex) {
                // If the server died or stalled during the load, just ignore it
                if (!(((uint)ex.ErrorCode) == 0x80010007 || ((uint)ex.ErrorCode) == 0x8001010A))
                    throw ex;
            }
            return true;
        }
    }

    /// <summary>
    /// This class implements a loader that loads tracks from the iTunes library
    /// by reading the iTunes XML liibrary file.
    /// </summary>
    public class FastITunesSongLoader : SongLoader
    {
        /// <summary>
        /// Do the actual work of loading the lyrics from the library
        /// </summary>
        /// <param name="e"></param>
        /// <returns>Ignored</returns>
        protected override object DoWork(DoWorkEventArgs e) {

            // How many tracks are there and how many songs should we fetch?
            int maxSongs = ITunes.Instance.AllTracks.Count;
            if (this.MaxSongsToFetch > 0)
                maxSongs = Math.Min(maxSongs, this.MaxSongsToFetch);

            // Load the whole xml file into memory, and then remove the DTD.
            // We remove that so we can read the xml even when not connected to the network
            string xml = File.ReadAllText(ITunes.Instance.XmlPath);
            int docTypeStart = xml.IndexOf("<!DOCTYPE");
            const string endOfDtdMarker = "0.dtd\">";
            int docTypeEnd = xml.IndexOf(endOfDtdMarker) + endOfDtdMarker.Length;
            string xml2 = xml.Remove(docTypeStart, docTypeEnd - docTypeStart);

            XPathDocument doc = new XPathDocument(new StringReader(xml2));
            XPathNavigator nav = doc.CreateNavigator();

            // Move to plist, then to master library, than tracks, then first track
            nav.MoveToChild("plist", "");
            nav.MoveToChild("dict", "");
            nav.MoveToChild("dict", "");
            bool success = nav.MoveToChild("dict", "");
            Dictionary<string, string> data = new Dictionary<string, string>();

            // Read each song until we have enough or no more
            while (success && this.Songs.Count < maxSongs && this.CanContinueRunning) {
                success = nav.MoveToFirstChild();

                // Read each piece of information about the song
                data.Clear();
                while (success) {
                    string key = nav.Value;
                    success = nav.MoveToNext();
                    string value = nav.Value;
                    data[key] = value;
                    success = nav.MoveToNext();
                }

                // Create and add the song if it's not one we want to ignore
                if (data.Count > 0 && !this.KindsToIgnore.Contains(this.GetValue(data, "Kind"))) {
                    ITunesSong song = new ITunesSong(
                        this.GetValue(data, "Name"),
                        this.GetValue(data, "Artist"),
                        this.GetValue(data, "Album"),
                        this.GetValue(data, "Genre"),
                        this.GetValue(data, "Persistent ID"));
                    this.AddSong(song);
                    this.ReportProgress((this.Songs.Count * 100) / maxSongs);
                }
                success = nav.MoveToParent();
                success = nav.MoveToNext("dict", "");
            }

            return true;
        }

        /// <summary>
        /// Get a value from a dictionary or return a default if that key isn't there
        /// </summary>
        /// <param name="data">The dictionary</param>
        /// <param name="key">The key to fetch</param>
        /// <returns>The value of the key or an empty string</returns>
        private string GetValue(Dictionary<string, string> data, string key) {
            string value;
            if (data.TryGetValue(key, out value))
                return value;
            else
                return "";
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Team Leader
Australia Australia
Phillip has been playing with computers since the Apple II was the hottest home computer available. He learned the fine art of C programming and Guru meditation on the Amiga.

C# and Python are his languages of choice. Smalltalk is his mentor for simplicity and beauty. C++ is to programming what drills are to visits to the dentist.

He worked for longer than he cares to remember as Lead Programmer and System Architect of the Objective document management system. (www.objective.com)

He has lived for 10 years in northern Mozambique, teaching in villages.

He has developed high volume trading software, low volume FX trading software, and is currently working for Atlassian on HipChat.

Comments and Discussions