Wednesday, January 6, 2010

Autogenerate Playlists for ITunes in Directory Order

Source code

Introduction.

This Article demonstrates a tool which can auto generate Playlists for ITunes. We can create playlists same as the folder structure from which they have been added to ITunes library. When we look into the ITunes playlist after autogeneration, a directory structure will be created. This folder contains all the songs in the exact makeup as in your Hard Disk.

Requirement.

When started using one of the best selling music player from Apple, I felt very difficult to find songs or music albums from the device. It is pretty much ok if we have a small number of songs. But for music freaks who carries tones and tones of music files with them, it's kinda painful to find a specific album or film. This player provides so many approaches to find and manage songs and albums nevertheless all of them just go off the purpose when the count of albums goes up. Undoubtfully, Apple's IPod is one of the most popular Music player in world and I expected ITunes provide some good way to handle it, which is not provided though.

Solution.

After all the researches which bought me in to a conclusion; at least in the viewpoint of desktop users, ITunes is not build considering large counts. Taking thoughts back to days which is used to hear songs from the desktop, I was used to arrange albums in folders grouped by months and years and also made different folders for different categories like Musicals, Albums, Film songs, different languages etc. Those days, I used to drag and drop folders into my favorite player Winamp and clear the playlist when ever that is not required, or save them as playlist for later use. Luckily this feature is partially available in IPod. I can create an 'On-The-Go' playlist in IPod on the fly and clear that whenever it is not required. But still one problem exist. How do I find the albums from the big collection say 1000, where I wanted to find only some songs released in Jan 2005? Now I understand the need to create a tool which can create songs on the same exact order of my Hard Disk. Finding the ITunes SDK during the research made my life easier, thanks to Apple.

How to Use the Application

Application UI Contains main 5 elements for user focus. You can use the default settings provided in the application. A small help which may be self explanatory is provided as the tool tip.

Steps:

1) Start the application and rename the Root folder if required.

2) Click on 'Sync Playlist' button.

3) Wait for some seconds to start the operation. You can see ITunes getting started and new folders and playlist getting created under specified playlist folder.

4) If you want to Abort the operation you can do it at any time. The progress so far will be saved in a work folder in root of ITunes.

5) If you prefer to keep a backup it will be kept in the Backup folder, however this will be updated to your IPod next time you sync it.

Using the Code:

The complete development cycle we will see in 4 phases.

1) Design of mock ITunes folder subsystem.

2) Design of ITunes Interfacing Systems.

3) Design of User Interface.

4) Performance Optimizations and User Experience improvements.

1) Design of mock ITunes Folder subsystem - The Model.

When I started investigating the architecture of ITunes I found that ITunes subsystem is not made in the viewpoint of handling a tree formation which I am in need of. At least it is not exposed as the part of API (or to the worst am I not able to figure it out?). For every step I need to find the element from the collection, which I didn't see viable solution as what I have is mainly the folder name which may invite duplicates. To overcome this problem I need to make my own tree subsystem in my Application.

Design extracted three categories of tree elements in ITunes subsystem which is essentially one base class in ITunes.

a) Folder, b) Playlist, c) Tracks.

Here is the code for this purpose.

a - FolderElement.cs

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using iTunesLib;

namespace ItnuesPlayListManager

{

public class FolderElement

{

public FolderElement(FolderElement parent,IITUserPlaylist itnuesfolder)

{

CreateNew(parent, itnuesfolder);

}

public FolderElement(FolderElement parent,string newfoldername)

{

IITUserPlaylist itnuesFolderSource = (IITUserPlaylist)parent.ItnuesFolderSource.CreateFolder(newfoldername);

CreateNew(parent, itnuesFolderSource);

}

private bool CreateNew(FolderElement parent,IITUserPlaylist itnuesfolder)

{

ItnuesFolderSource = itnuesfolder;

this.Parent = parent;

SubFolders = new SortedDictionary<string, FolderElement>();

PlayLists = new SortedDictionary<string, PlayListElement>();

return true;

}

public FolderElement Parent

{

get;

private set;

}

public IITUserPlaylist ItnuesFolderSource

{

get;

private set;

}

public SortedDictionary<string, FolderElement> SubFolders

{

get;

private set;

}

public SortedDictionary<string, PlayListElement> PlayLists

{

get;

private set;

}

public bool MoveFolder(FolderElement destination)

{

if(destination.SubFolders.ContainsKey(this.ItnuesFolderSource.Name))

return false;

Parent = destination;

Parent.SubFolders.Remove(this.ItnuesFolderSource.Name);

destination.SubFolders.Add(this.ItnuesFolderSource.Name, this);

object val = destination.ItnuesFolderSource;

this.ItnuesFolderSource.set_Parent(ref val);

return true;

}

public bool DeleteSubFolder(FolderElement folder)

{

if (!this.SubFolders.ContainsKey(folder.ItnuesFolderSource.Name))

return false;

this.SubFolders.Remove(folder.ItnuesFolderSource.Name);

folder.ItnuesFolderSource.Delete();

return true;

}

}

}

b - PlaylistElement.cs

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using iTunesLib;

namespace ItnuesPlayListManager

{

public class FolderElement

{

public FolderElement(FolderElement parent,IITUserPlaylist itnuesfolder)

{

CreateNew(parent, itnuesfolder);

}

public FolderElement(FolderElement parent,string newfoldername)

{

IITUserPlaylist itnuesFolderSource = (IITUserPlaylist)parent.ItnuesFolderSource.CreateFolder(newfoldername);

CreateNew(parent, itnuesFolderSource);

}

private bool CreateNew(FolderElement parent,IITUserPlaylist itnuesfolder)

{

ItnuesFolderSource = itnuesfolder;

this.Parent = parent;

SubFolders = new SortedDictionary<string, FolderElement>();

PlayLists = new SortedDictionary<string, PlayListElement>();

return true;

}

public FolderElement Parent

{

get;

private set;

}

public IITUserPlaylist ItnuesFolderSource

{

get;

private set;

}

public SortedDictionary<string, FolderElement> SubFolders

{

get;

private set;

}

public SortedDictionary<string, PlayListElement> PlayLists

{

get;

private set;

}

public bool MoveFolder(FolderElement destination)

{

if(destination.SubFolders.ContainsKey(this.ItnuesFolderSource.Name))

return false;

Parent = destination;

Parent.SubFolders.Remove(this.ItnuesFolderSource.Name);

destination.SubFolders.Add(this.ItnuesFolderSource.Name, this);

object val = destination.ItnuesFolderSource;

this.ItnuesFolderSource.set_Parent(ref val);

return true;

}

public bool DeleteSubFolder(FolderElement folder)

{

if (!this.SubFolders.ContainsKey(folder.ItnuesFolderSource.Name))

return false;

this.SubFolders.Remove(folder.ItnuesFolderSource.Name);

folder.ItnuesFolderSource.Delete();

return true;

}

}

}

c - TrackElement.cs

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using iTunesLib;

namespace ItnuesPlayListManager

{

public class TrackElement

{

public TrackElement(PlayListElement playlistobject, IITTrack trackobject)

{

ItnuesTrackSource = trackobject;

object obj = (object)trackobject;

playlistobject.ItnuesFolderSource.AddTrack(ref obj);

Parent = playlistobject;

}

public PlayListElement Parent

{

get;

private set;

}

public IITTrack ItnuesTrackSource

{

get;

private set;

}

}

}

2) Design of ITunes Interfacing Systems.

After completing design of mock folder system we should provide an interface to perform the functionalities which communicates with ITunes. Here are the classes which performs these operations.

ItnuesApp.cs

using System.Collections.Generic;

using iTunesLib;

namespace ItnuesPlayListManager

{

public static class ItnuesApp

{

static iTunesApp app;

public static iTunesApp Application

{

get

{

if(app ==null)

app = new iTunesAppClass();

return app;

}

}

public static bool Disconnect()

{

app = null;

return true;

}

public static PlayListElement GetPlaylist(FolderElement root, List<string> path)

{

PlayListElement leaf;

FolderElement currentfolder = root;

int i = 0;

for (i = 1; i <>

{

if (currentfolder.SubFolders.ContainsKey(path[i]) == false)

{

FolderElement folder = new FolderElement(currentfolder, path[i]);

currentfolder.SubFolders.Add(path[i], folder);

currentfolder = folder;

}

else

{

currentfolder = currentfolder.SubFolders[path[i]];

}

}

if (currentfolder.PlayLists.ContainsKey(path[i]) == true)

{

leaf = currentfolder.PlayLists[path[i]];

}

else

{

leaf = new PlayListElement(currentfolder, path[i]);

currentfolder.PlayLists.Add(path[i], leaf);

}

return leaf;

}

public static FolderElement GetWorkingRootFolder(string rootfoldername)

{

IITUserPlaylist rootworkingfolder = IsDuplicateSubFolderExists(null, rootfoldername + ROOTFOLDERNAME_WORKING);

if (rootworkingfolder != null)

{

rootworkingfolder.Delete();

}

rootworkingfolder = (IITUserPlaylist)ItnuesApp.Application.CreateFolder(rootfoldername + ROOTFOLDERNAME_WORKING);

return new FolderElement(null, rootworkingfolder);

}

public static bool ManageBackup(FolderElement newrootfolder, string rootfoldername, bool keepabackup)

{

IITUserPlaylist rootfoldebackup = IsDuplicateSubFolderExists(null, rootfoldername + ROOTFOLDERNAME_BACKUP);

IITUserPlaylist rootfoldercurrent = IsDuplicateSubFolderExists(null, rootfoldername);

if (rootfoldebackup != null)

{

rootfoldebackup.Delete();

}

if (rootfoldercurrent != null)

{

if (keepabackup == true)

{

rootfoldercurrent.Name = rootfoldername + ROOTFOLDERNAME_BACKUP;

}

else

{

rootfoldercurrent.Delete();

}

}

newrootfolder.ItnuesFolderSource.Name = rootfoldername;

return true;

}

public static IITUserPlaylist IsDuplicateSubFolderExists(IITUserPlaylist play, string foldername)

{

foreach (object item in ItnuesApp.Application.LibrarySource.Playlists)

{

//avoids cast error as some may not be of this type.

if (item is IITUserPlaylist)

{

IITUserPlaylist itemIITUserPlaylist = (IITUserPlaylist)item;

if (itemIITUserPlaylist.Name.ToUpper() == foldername.ToUpper())

{

//if the current folder is root then parent will be null and so this will not be the one as, the duplicate subfolder should be having the same parent not 'NULL'

if (itemIITUserPlaylist.get_Parent() != null)

{

if (itemIITUserPlaylist.get_Parent().playlistID == play.playlistID)

return itemIITUserPlaylist;

}

else //only exception is checking duplicate for root folder where we will get GetParent = null

return itemIITUserPlaylist;

}

}

}

return null;

}

}

}

InteractionUtils.cs

using System;

using System.Collections.Generic;

using System.Linq;

using System.Windows.Threading;

namespace ItnuesPlayListManager

{

public static class InteractionUtils

{

///

public static List<string> GetBrokenPaths(string path)

{

List<string> list = new List<string>();

string[] str = path.Split(new char[] { '\\', ':' }, 999, StringSplitOptions.RemoveEmptyEntries);

list.AddRange(str.AsEnumerable());

return list;

}

public static void RemoteThreadUpdate(this Dispatcher ctrlControl, Action mtdLambadaExpression)

{

if (ctrlControl.CheckAccess())

{

mtdLambadaExpression();

}

else

{

ctrlControl.BeginInvoke(DispatcherPriority.Normal, mtdLambadaExpression);

}

}

}

public class DataEventArgs : EventArgs

{

public DataEventArgs(T data)

{

this.Data = data;

}

public T Data { get; set; }

}

}

3) Design of User Interface.

Finally when considering the design of UI Interface it appeared that using MVC Pattern would be appropriate as the UI functionality seems to become complex. The controller contains the integration logic between ITunes Interface and mock folder system and finally provides the output to the UI. This section consists of three components Dashboard code behind, Dashboard Controller and finally the Dashboard UI.

DashBoardController.cs

using System;

using System.Collections.Generic;

using System.Linq;

using iTunesLib;

using System.Windows.Documents;

namespace ItnuesPlayListManager

{

public class DashBoardController

{

public class CreateEventArgs

{

public CreateEventArgs(string message,double progress,bool isErrorMessage)

{

Message = message;

Progress = progress;

IsErrorMessage = isErrorMessage;

}

public string Message { get; private set; }

public double Progress { get; private set; }

public bool IsErrorMessage { get; private set; }

}

public event EventHandler<DataEventArgs<CreateEventArgs>> ProgressStatus;

public event EventHandler<DataEventArgs<double>> BeforeCreate;

public event EventHandler<DataEventArgs<bool>> AfterCreate;

public FolderElement Root { get; private set; }

public void CreatePlaylistTree(object obj)

{

object[] objarray = (object[])obj;

bool val = CreatePlaylistTree((string)objarray[0], (bool)objarray[1], (bool)objarray[2],(bool)objarray[3]);

if (AfterCreate != null) AfterCreate(this,new DataEventArgs<bool>(val));

}

public bool CreatePlaylistTree(string rootfoldername,bool deleteUnfoundtracks,bool keepbackup,bool removeEmptyFolders)

{

List<IITFileOrCDTrack> trackstodelete = new List<IITFileOrCDTrack>();

var tracks = ItnuesApp.Application.LibraryPlaylist.Tracks;

var numTracks = tracks.Count;

int i=1;

PlayListElement element = null;

string lastlocation = string.Empty;

if(BeforeCreate!=null)

BeforeCreate(this, new DataEventArgs<double>(numTracks));

Root = ItnuesApp.GetWorkingRootFolder(rootfoldername);

//start create the playlists and folders

for (i = 1; i <>

{

IITFileOrCDTrack currTrack = (IITFileOrCDTrack)tracks[i];

//temporary arrangement. skip the tracks of those the location is not found. TODO.need to find/remove/save the location of these files.

if (currTrack.Location != null)

{

/*an optimization logic.

* checks the current plalist is the same as last playlist.

* So we can avoid going through loops to findout the tree.*/

string currentlocation = currTrack.Location.Substring(0,currTrack.Location.LastIndexOf("\\"));

if (lastlocation == currentlocation)

{

element.SubTracks.Add(currTrack.Location.Substring(currTrack.Location.LastIndexOf("\\")),new TrackElement(element, tracks[i]));

}

else

{

List<string> patharray = InteractionUtils.GetBrokenPaths(currTrack.Location);

element = ItnuesApp.GetPlaylist(Root, patharray);

element.SubTracks.Add(patharray[patharray.Count - 1], new TrackElement(element, tracks[i]));

}

if(ProgressStatus!=null) ProgressStatus(this, new DataEventArgs<CreateEventArgs>(new CreateEventArgs("Created [" + currTrack.Location + "] - [" + currTrack.Name + "]", i, false)));

lastlocation = currentlocation;

}

else

{

if(deleteUnfoundtracks ==true)

{

if (ProgressStatus != null) ProgressStatus(this, new DataEventArgs<CreateEventArgs>(new CreateEventArgs("Deleted the track [" + currTrack.Name + "]", i, true)));

trackstodelete.Add(currTrack);

}

}

}

//optionaly deletes the missing tracks.

foreach (var item in trackstodelete)

{

item.Delete();

}

RemoveEmptyFolders(removeEmptyFolders);

ItnuesApp.ManageBackup(Root, rootfoldername, keepbackup);

return true;

}

private void RemoveEmptyFolders(bool removeEmptyFolders)

{

//deletes the unrequired subfolders

if (removeEmptyFolders == true)

{

if (ProgressStatus != null) ProgressStatus(this, new DataEventArgs<CreateEventArgs>(new CreateEventArgs("Removing unrequired folders", 0, false)));

FolderElement singlefolder = Root;

while (singlefolder.SubFolders.Count <= 1)

{

singlefolder = singlefolder.SubFolders.ElementAt(0).Value;

}

if (singlefolder != Root)

{

FolderElement foldertodelete = Root.SubFolders.ElementAt(0).Value;

foreach (var item in singlefolder.SubFolders)

{

item.Value.MoveFolder(Root);

}

Root.DeleteSubFolder(foldertodelete);

}

}

}

}

}

DashBoard.xaml.cs

using System;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Media;

using System.Threading;

namespace ItnuesPlayListManager

{

///

/// Interaction logic for DashBoard.xaml

///

public partial class DashBoard : Window

{

Thread navigatethread;

double MaxTracks = 0;

public DashBoard()

{

InitializeComponent();

//the scripting object will be locked even after utility terminates. so need to explicitily release those.

Application.Current.Exit+=new ExitEventHandler((x, y) =>

{

ItnuesApp.Disconnect();

ItnuesPlayListManager.Properties.Settings.Default.Save();

}

);

}

private void Window_Loaded(object sender, RoutedEventArgs e)

{

this.chkRemoveUnfoundtracks.IsChecked = ItnuesPlayListManager.Properties.Settings.Default.RemoveUnFoundTracks;

this.chkBackCurrent.IsChecked = ItnuesPlayListManager.Properties.Settings.Default.KeepBackup;

this.txtRootFolderName.Text = ItnuesPlayListManager.Properties.Settings.Default.AutoString;

this.chkRemoveUnusedRoots.IsChecked = ItnuesPlayListManager.Properties.Settings.Default.RemoveUnusedRoots;

chkRemoveUnfoundtracks.Checked += new RoutedEventHandler((x, y) =>

{

if (MessageBox.Show("This will remove all the tracks from ITnues which the files cannot be located. Files from Removable Media or Network Locations also will be affected. " + Environment.NewLine + Environment.NewLine + "Checking this option is recommended only if you have all the files in your local Hard Disk. Are You sure you want to check this option?", "File Removal", MessageBoxButton.YesNo,MessageBoxImage.Question) == MessageBoxResult.No)

{

chkRemoveUnfoundtracks.IsChecked = false;

return;

}

ItnuesPlayListManager.Properties.Settings.Default.RemoveUnFoundTracks = true;

});

chkBackCurrent.Checked += new RoutedEventHandler((x, y) =>

{

ItnuesPlayListManager.Properties.Settings.Default.KeepBackup = true;

});

chkRemoveUnusedRoots.Checked+=new RoutedEventHandler((x,y) =>

{

ItnuesPlayListManager.Properties.Settings.Default.RemoveUnusedRoots = true;

});

chkRemoveUnfoundtracks.Unchecked += new RoutedEventHandler((x, y) =>

{

ItnuesPlayListManager.Properties.Settings.Default.RemoveUnFoundTracks = false;

});

chkBackCurrent.Unchecked += new RoutedEventHandler((x, y) =>

{

ItnuesPlayListManager.Properties.Settings.Default.KeepBackup = false;

});

chkRemoveUnusedRoots.Unchecked += new RoutedEventHandler((x, y) =>

{

ItnuesPlayListManager.Properties.Settings.Default.RemoveUnusedRoots = false;

});

txtRootFolderName.TextChanged += new TextChangedEventHandler((x, y) =>

{

ItnuesPlayListManager.Properties.Settings.Default.AutoString = txtRootFolderName.Text;

});

}

private void btnsyncplaylist_Click(object sender, RoutedEventArgs e)

{

try

{

if (txtRootFolderName.Text.Trim().Length <>

{

MessageBox.Show("Root folder name cannot be empty.");

return;

}

if (navigatethread == null)

{

DashBoardController playlistmanager = new DashBoardController();

playlistmanager.ProgressStatus += new EventHandler<DataEventArgs<DashBoardController.CreateEventArgs>>(playlistmanager_ProgressStatus);

playlistmanager.BeforeCreate += new EventHandler<DataEventArgs<double>>((x, y) => { MaxTracks = y.Data; });

playlistmanager.AfterCreate += new EventHandler<DataEventArgs<bool>>(playlistmanager_AfterCreate);

lstOutput.Items.Clear();

lstOutput.Items.Add("Connecting to ITnues Application..");

barProgress.Value = 0;

navigatethread = new Thread(new ParameterizedThreadStart(playlistmanager.CreatePlaylistTree));

navigatethread.Start(new object[] {txtRootFolderName.Text, chkRemoveUnfoundtracks.IsChecked.Value,chkBackCurrent.IsChecked.Value,chkRemoveUnusedRoots.IsChecked.Value});

btnsyncplaylist.Content = "Abort";

}

else

{

navigatethread.Abort();

navigatethread = null;

btnsyncplaylist.Content = "Sync Playlist";

}

}

catch (Exception ex)

{

lstOutput.Items.Add(ex.Message);

}

}

void playlistmanager_AfterCreate(object sender, DataEventArgs<bool> e)

{

btnsyncplaylist.Dispatcher.RemoteThreadUpdate(() =>

{

btnsyncplaylist.Content = "Sync Playlist";

navigatethread = null;

});

}

void playlistmanager_ProgressStatus(object sender, DataEventArgs<DashBoardController.CreateEventArgs> e)

{

lstOutput.Dispatcher.RemoteThreadUpdate(() =>

{

if (e.Data.IsErrorMessage == false)

lstOutput.Items.Insert(0, e.Data.Message);

else

{

ListBoxItem item = new ListBoxItem();

item.Foreground = Brushes.Red;

item.Content = e.Data.Message;

lstOutput.Items.Insert(0,item);

}

});

barProgress.Dispatcher.RemoteThreadUpdate(() =>

{

barProgress.Value = (e.Data.Progress / MaxTracks) * 100;

}

);

}

}

}

4) Performance Optimizations and User Experience improvements.

OK. Finally we got the Application compiled and running. If you try to run the application you can see The application creates subfolders in ITunes same as the structure of your Songs folder in your Hard Disk. Evidently you need to configure ITunes not to copy your all songs files into ITunes folder which you will have no control over the location of your files. I guess this is the way a person would like if he got lot of music in his pocket as he want better control over his music files [at least it works perfect for me :)].

Application is Up and Running properly it creates the playlist now. But I want to bring out some performance tweaks and user experience items added to the application.

1) Multithreading components - Stop the UI from Freezing during the lengthy operation.

We are able to see the output in ITunes however our Application was not responding until the operation gets finished. This is because the UI and background operation is done in the same thread and so we need to move out the ITunes operation from main thread. We created another thread for that.

Section 1)

This code you can find in class InteractionUtils. This Extension method attaches to Dispatcher objects which we can pass a Lambda Expression or Action instance to perform the operation back in main thread.

public static void RemoteThreadUpdate(this Dispatcher ctrlControl, Action mtdLambadaExpression)

{

if (ctrlControl.CheckAccess())

{

mtdLambadaExpression();

}

else

{

ctrlControl.BeginInvoke(DispatcherPriority.Normal, mtdLambadaExpression);

}

}

Section 2)

A new thread is instantiated inside btnsyncplaylist_Click(object sender, RoutedEventArgs e) in DashBorad.xaml.cs

navigatethread = new Thread(new ParameterizedThreadStart(playlistmanager.CreatePlaylistTree));

navigatethread.Start(new object[] {txtRootFolderName.Text, chkRemoveUnfoundtracks.IsChecked.Value,chkBackCurrent.IsChecked.Value,chkRemoveUnusedRoots.IsChecked.Value});

Related overloading for passing data into business layer, DashBoardController.cs

public void CreatePlaylistTree(object obj)

{

object[] objarray = (object[])obj;

bool val = CreatePlaylistTree((string)objarray[0], (bool)objarray[1], (bool)objarray[2],(bool)objarray[3]);

if (AfterCreate != null) AfterCreate(this,new DataEventArgs(val));

}

Section 3)

For updating UI with progress and operation log require messages passed back from sub thread. This is achieved through some events in DashBoardController.cs.

public event EventHandler> ProgressStatus;

public event EventHandler> BeforeCreate;

public event EventHandler> AfterCreate;

These Events are subscribed back in Dashboard.xaml.cs

void playlistmanager_AfterCreate(object sender, DataEventArgs e)

void playlistmanager_ProgressStatus(object sender, DataEventArgs e)

2) Looping optimizations:

Section 1)

ITunes doesn't provide a mechanism to find a playlist/Folder/tracks exists inside a specific folder. ITunes SDK provides some limited search functionalities but for attain a functionality like this in a highly performing way we need to create our own search algorithms. The creation of Folder system resolved this issue. [Really speaking Initially when I did R&D on ITunes SDK I noticed this functionality is missing and I decided to create a tree graph system so that application should be able to navigate up and down through the trees].

Section 2)

We got some improvements left. If you debug through the list of tracks(...LibraryPlaylist.Tracks), you can find that most cases the Tracks are consecutive order and each folder can contain multiple tracks. So there is no need to navigate from root to leaf if the current track is in the same playlist as the last track.

This is achieved by a simple location check,

if (lastlocation == currentlocation)

in the method

public bool CreatePlaylistTree(string rootfoldername,bool deleteUnfoundtracks,bool keepbackup,bool removeEmptyFolders)

The difference you can notice while running the application. You can see a small pauses in the output when a new folder location reaches which would have been the performance if this optimization has not been implemented.

3) Optionally removes the missing tracks.

In the method,

public bool CreatePlaylistTree(string rootfoldername,bool deleteUnfoundtracks,bool keepbackup,bool removeEmptyFolders)

you can see this code which removes the tracks which Location is null. I know Itunes stores the original location soemwhere which I couldn't findout. Probabaly it is not exposed to outside through COM API.

if (currTrack.Location != null)

{

.....

}

else

{

if(deleteUnfoundtracks ==true)

{

if (ProgressStatus != null) ProgressStatus(this, new DataEventArgs(new CreateEventArgs("Deleted the track [" + currTrack.Name + "]", i, true)));

trackstodelete.Add(currTrack);

}

}

....

foreach (var item in trackstodelete)

{

item.Delete();

}

4) Optionally removes empty folder from Root.

This feature is useful if main music folder starts somewhere inside the subfolders like Itnues Music folder. I am sure while naviagting through Itunes people donot want to navigate through a series of empty playlist folders to reach one of the subfolders. You can find this code in method,

private void RemoveEmptyFolders(bool removeEmptyFolders)

5) Disconnect ITunes properly.

ITunes instnce will be locked even after utility terminates. This happens because .Net cannot release the COM resources even after the application terminates. So we need to release those explicitily.

Application.Current.Exit+=new ExitEventHandler((x, y) =>

{

ItnuesApp.Disconnect();

ItnuesPlayListManager.Properties.Settings.Default.Save();

});

Exceptions:

1) The tool does consider only the titles of which the physical paths can be found. Typically If you open ITunes and if you see an exclamation(!) mark near to your tracks, those will not be added to the Auto generated playlist. I am working upon this and you can expect a future release with this fix.

2) It is recommended to set the options in ITunes so that it should not copy all the files added into library into ITunes local folder. Few files are ok like those which are converted. Too many of them will end up in the same situation which happened to default features of ITunes, having too many playlist in one folder called 'My Music'.

System Requirements

This software is known to work with the following configuration:

  • Microsoft Windows XP SP1/Vista/Windows7
  • Microsoft .NET Framework 3.5
  • Apple iTunes 9.0 or later

The software may possibly work under different configurations, but this has not be verified to date.

Conclusion

I expect this article to help people who want to have a small tool which they can get rid of the pain in finding music in their apple music devices. Though there are a lot in MAC platform, I have not seen much people doing interfaces to ITunes in extensive way in windows platform. I want to give a break to those who want to have a starter example. please do comments and improvements.


Bookmark