using System; using System.IO; using System.Xml; using System.Text; using System.Drawing; using System.Windows.Forms; using System.Runtime.InteropServices; public class CDSetup:Form { private Button btnAdd; private Button btnCopy; private EventHandler copyHandler; private EventHandler addHandler; private ListBox playlists; private ListBox toCopy; private string iTunesLibrary = null; private string stagingArea = null; private XmlDataDocument itunesDoc; // http://www.codeproject.com/csharp/csdoesshell1.asp [DllImport("shell32.dll")] public static extern Int32 SHGetFolderPath(IntPtr a, Int32 b, IntPtr c, UInt32 d, StringBuilder e); // stolen from the platform SDK header files const int CSIDL_CDBURN_AREA = 59; public CDSetup() { ClientSize = new System.Drawing.Size(400, 300); Text = "ITUNES PLAYLISTS => MP3 CD"; addHandler = new EventHandler(AddPlaylist); btnAdd = new Button(); btnAdd.Size = new Size(80,30); btnAdd.Location = new Point(310,10); btnAdd.Text="Add"; btnAdd.Click += addHandler; Controls.Add(btnAdd); copyHandler = new EventHandler(CopyCd); btnCopy = new Button(); btnCopy.Size = new Size(80,30); btnCopy.Location = new Point(310,260); btnCopy.Text="Copy"; btnCopy.Click += copyHandler; Controls.Add(btnCopy); playlists = new ListBox(); playlists.Size = new Size(140,280); playlists.Location = new Point(10,10); Controls.Add(playlists); toCopy = new ListBox(); toCopy.Size = new Size(140,280); toCopy.Location = new Point(160,10); Controls.Add(toCopy); Console.WriteLine("Looking for necessary files..."); try { iTunesLibrary = FindItunesLibrary(); Console.WriteLine("Found iTunes library at " + iTunesLibrary); stagingArea = FindStagingArea(); Console.WriteLine("Found staging area at " + stagingArea); itunesDoc = new XmlDataDocument(); StreamReader sr = new StreamReader(iTunesLibrary); itunesDoc.Load(sr); sr.Close(); XmlNodeList list = itunesDoc.SelectNodes("/plist/dict/key[text()='Playlists']/following-sibling::array/dict"); foreach (XmlNode node in list) playlists.Items.Add(node.SelectSingleNode("key[text()='Name']/following-sibling::string").InnerText); } catch (Exception ex) { Console.WriteLine("Initialization error: " + ex.Message); } } // I'm not sure if iTunes can be configured to live somewhere else. // Also not sure if locale affects its file names. private static string FindItunesLibrary() { string location = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic); if (File.Exists(location + "\\iTunes\\iTunes Music Library.xml")) return location + "\\iTunes\\iTunes Music Library.xml"; throw new Exception("Unable to locate iTunes library"); } private static string FindStagingArea() { StringBuilder location = new StringBuilder(1024); SHGetFolderPath(IntPtr.Zero, CSIDL_CDBURN_AREA, IntPtr.Zero, 0, location); if (Directory.Exists(location.ToString())) return location.ToString(); throw new Exception("Unable to locate staging area"); } private void AddPlaylist(Object source, EventArgs e) { if (playlists.SelectedItem != null) toCopy.Items.Add(playlists.Text); } // that .Substring(1) is ugly, but I couldn't figure out a better way to get // the local path from file://localhost/C:/whatever. public static string FileNameFromUri(string address) { Uri uri = new Uri(address); if (uri.IsFile && uri.Host == "localhost") return System.Web.HttpUtility.UrlDecode(uri.PathAndQuery).Substring(1); throw new Exception("URI does not point to a local file"); } // since this is an event handler, it'll probably lock up the window while it's // adding songs to burn. That could take a while since xpath is slow and the // document here can be huge. I don't know how to launch a thread in C#. private void CopyCd(Object source, EventArgs e) { try { foreach (string playlist in toCopy.Items) { Directory.CreateDirectory(stagingArea + "\\" + playlist); System.Console.WriteLine("Searching playlist "+playlist); // longest xpath query I ever wrote. XmlNodeList tracks = itunesDoc.SelectNodes("/plist/dict/key[text()='Playlists']/following-sibling::array/dict/key[text()='Name']/following-sibling::string[text()='"+playlist+"']/following-sibling::key[text()='Playlist Items']/following-sibling::array/dict/key[text()='Track ID']/following-sibling::integer"); if (tracks == null) throw new Exception("Unable to find tracks for this playlist, probably an XPath error or iTunes changed their format"); foreach (XmlNode track in tracks) { XmlNode trackNode = itunesDoc.SelectSingleNode("/plist/dict/key[text()='Tracks']/following-sibling::dict/key[text()='"+track.InnerText+"']/following-sibling::dict"); if (trackNode == null) throw new Exception("Unable to find track node, probably an XPath error or iTunes changed their format"); XmlNode trackFile = trackNode.SelectSingleNode("key[text()='Location']/following-sibling::string"); if (trackFile == null) throw new Exception("Unable to find track file, probably an XPath error or iTunes changed their format"); XmlNode trackType = trackNode.SelectSingleNode("key[text()='Track Type']/following-sibling::string"); // we only want files, not streams if (trackType == null || trackType.InnerText != "File") { System.Console.WriteLine("Skipping track of unknown type: " + ((trackType == null) ? "" : trackType.InnerText)); continue; } // only MP3 files, not ACC XmlNode trackKind = trackNode.SelectSingleNode("key[text()='Kind']/following-sibling::string"); if (trackKind == null || trackKind.InnerText != "MPEG audio file") { System.Console.WriteLine("Skipping track of unknown kind: " + ((trackKind == null) ? "" : trackKind.InnerText)); continue; } try { string filePath = FileNameFromUri(trackFile.InnerText); if (filePath != null && File.Exists(filePath)) File.Copy(filePath, stagingArea + "\\" + playlist + "\\" + Path.GetFileName(filePath)); else throw new Exception("Can't find " + Path.GetFileName(filePath)); } catch(Exception ex) { System.Console.WriteLine("Error: " + ex.Message); } } } MessageBox.Show("No fatal errors occurred.", "All done."); } catch (Exception ex) { Console.WriteLine("Serious Error: " + ex.Message); MessageBox.Show("ERRORED!", "ERRORED!"); } } static void Main() { Application.Run(new CDSetup()); } }