Introduction
This article demonstrates a tool which can auto generate Playlists for iTunes. We can create playlists the 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 that is in your Hard Disk.
Requirement
When using one of the best selling music player from Apple, I found it 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 carry tons and tons of music files with them, it's kind of painful to find a specific album or film. This player provides so many approaches to find and manage songs and albums, yet all of them become somewhat useless when the count of albums goes up. Undoubtedly, Apple's iPod is one of the most popular music players in world and I expected iTunes to provide a good way to handle selection - but they do not.
Solution
All my research bought me to one conclusion: At least in the viewpoint of desktop users, iTunes was not built while considering large counts. Thinking back to days when I used to listen to songs from the desktop, I 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 whenever I wanted, or save them as playlist for later use. Luckily this feature is partially available in iTunes. 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 big collections of say 1000, where I wanted to find only some songs released in Jan 2005? I needed to create a tool which could create songs on in the same exact order the appear on my Hard Disk. Finding the iTunes SDK during my research made my life easier. Thanks Apple.
How to Use the Application
The application UI contains five main elements. You can use the default settings provided in the application. There is even some small help provided as the tool tip.
Steps:
- Start the application and rename the Root folder if required.
- Click on 'Sync Playlist' button.
- 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.
- 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.
- 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 four phases.
- Design of mock iTunes folder subsystem.
- Design of iTunes Interfacing Systems.
- Design of User Interface.
- 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 the iTunes subsystem is not made in the viewpoint of handling a tree formation which I needed. At least it is not exposed as the part of API (or worse, am I not able to figure it out?). For every step I need to find the element from the collection, and I didn't see a viable solution to haivng mainly the folder name which may invite duplicates. To overcome this problem I needed 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 PlayListElement
{
public PlayListElement(FolderElement parent,IITUserPlaylist itnuesfolder)
{
ItnuesFolderSource = itnuesfolder;
this.Parent = parent;
SubTracks = new SortedDictionary<string, TrackElement>();
}
public PlayListElement(FolderElement parent, string newplaylistname)
{
ItnuesFolderSource = (IITUserPlaylist)parent.ItnuesFolderSource.CreatePlaylist(
newplaylistname);
this.Parent = parent;
SubTracks = new SortedDictionary<string, TrackElement>();
}
public FolderElement Parent
{
get; private set;
}
public IITUserPlaylist ItnuesFolderSource
{
get; private set;
}
public SortedDictionary<string, TrackElement> SubTracks
{
get; private set;
}
}
}
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 the 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 const string ROOTFOLDERNAME_BACKUP = "(Backup)";
public const string ROOTFOLDERNAME_WORKING = "(Working)";
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 < path.Count - 2; 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)
{
if (item is IITUserPlaylist)
{
IITUserPlaylist itemIITUserPlaylist = (IITUserPlaylist)item;
if (itemIITUserPlaylist.Name.ToUpper() == foldername.ToUpper())
{
if (itemIITUserPlaylist.get_Parent() != null)
{
if (itemIITUserPlaylist.get_Parent().playlistID == play.playlistID)
return itemIITUserPlaylist;
}
else
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<T> : 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);
for (i = 1; i < numTracks; i++)
{
IITFileOrCDTrack currTrack = (IITFileOrCDTrack)tracks[i];
if (currTrack.Location != null)
{
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)));
}
}
}
foreach (var item in trackstodelete)
{
item.Delete();
}
if (removeEmptyFolders == true)
{
if (ProgressStatus != null) ProgressStatus(this,
new DataEventArgs<CreateEventArgs>(new CreateEventArgs(
"Removing unrequired folders", i, 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);
}
}
ItnuesApp.ManageBackup(Root, rootfoldername, keepbackup);
return true;
}
}
}
DashBoard.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Threading;
namespace ItnuesPlayListManager
{
public partial class DashBoard : Window
{
Thread navigatethread;
double MaxTracks = 0;
public DashBoard()
{
InitializeComponent();
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 < 1)
{
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 the same as the structure of your Songs folder on your Hard Disk. Evidently you need to configure iTunes not to copy your all song files into iTunes folders because then you will have no control over the location of your files. This is way you can get a lot of music your pocket and get better control over your music files (at least it works perfect for me).
The application is up and running properly and it creates the playlist now. But I want to bring out some performance tweaks and have some 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 was finished. This is because the UI and background operation is done in the same thread and so we need to move the iTunes operation from the main thread. So we creat another thread for that.
Section 1)
This code can be found in the InteractionUtils
class. 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<bool>(val));
}
Section 3)
Updating UI with progress and operation log requires messages passed back from sub thread. This is achieved through some events in DashBoardController.cs.
public event EventHandler<DataEventArgs<CreateEventArgs>> ProgressStatus;
public event EventHandler<DataEventArgs<double>> BeforeCreate;
public event EventHandler<DataEventArgs<bool>> AfterCreate;
These events are subscribed back in Dashboard.xaml.cs
void playlistmanager_AfterCreate(object sender, DataEventArgs<bool> e)
void playlistmanager_ProgressStatus(object sender,
DataEventArgs<DashBoardController.CreateEventArgs> e)
2) Looping Optimizations
Section 1)
iTunes doesn't provide a mechanism to find a playlist/folder/tracks that exist inside a specific folder. iTunes SDK provides some limited search functionalities but to 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. (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 the application would 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 find that in most cases the tracks are in 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)
This optimization creates a noticable while running the application. Before you could see small pauses in the output when a new folder was located.
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<CreateEventArgs>(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 the main music folder starts somewhere inside the subfolders like the iTunes music folder. I am sure while naviagting through iTunes people do not want to navigate through a series of empty playlist folders to reach one of the subfolders. You can find this code in the method:
private void RemoveEmptyFolders(bool removeEmptyFolders)
5) Disconnect iTunes Properly
iTunes instnce will be locked even after the 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
- 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.
- 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. A 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 the MAC platform, I have not seen many people doing interfaces to iTunes in the Windows platform. I want to give a break to those who want to have a starter example. Please add comments, suggestions, and improvements on the forum below.