65.9K
CodeProject 正在变化。 阅读更多。
Home

按目录顺序为 iTunes 自动生成播放列表

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2010年1月12日

GPL3

8分钟阅读

viewsIcon

31551

downloadIcon

552

本文演示了一个可以自动为 iTunes 生成播放列表的工具。

App_Preview.jpg

引言

本文演示了一个可以自动生成 iTunes 播放列表的工具。我们可以根据添加歌曲到 iTunes 库的文件夹结构来创建播放列表。在自动生成后查看 iTunes 播放列表时,会创建一个目录结构。这个文件夹将包含与您的硬盘中的歌曲完全相同的构成。

要求

在使用苹果公司最畅销的音乐播放器之一时,我发现很难从设备中找到歌曲或音乐专辑。如果我们拥有的歌曲数量很少,那也还可以。但对于随身携带大量音乐文件的音乐爱好者来说,要找到特定的专辑或影片就有点痛苦了。这个播放器提供了很多查找和管理歌曲和专辑的方法,但当专辑数量增加时,它们都变得有点无用。毫无疑问,苹果的 iPod 是世界上最受欢迎的音乐播放器之一,我曾期望 iTunes 能提供一种好的选择方式——但它们没有。

解决方案

我所有的研究得出了一个结论:至少从桌面用户的角度来看,iTunes 在设计时并没有考虑到大量的音乐收藏。回想我过去在桌面上听歌的日子,我习惯按月份和年份将专辑分类存放,还会为不同的类别创建不同的文件夹,如音乐剧、专辑、电影歌曲、不同语言等。那些日子里,我习惯将文件夹拖放到我最喜欢的播放器 Winamp 中,想清空播放列表时就清空,或者保存为播放列表供以后使用。幸运的是,iTunes 部分提供了这个功能。我可以在 iPod 上即时创建“随身携带”播放列表,并在不再需要时清空它。但仍然存在一个问题。如何从大量的音乐收藏中找到我想要在 2005 年 1 月发布的特定歌曲?我需要一个工具,能够按照它们在我硬盘上的出现顺序创建歌曲。在我研究期间找到 iTunes SDK 让我的生活变得更轻松。感谢苹果。

如何使用该应用程序

该应用程序的 UI 包含五个主要元素。您可以使用应用程序中提供的默认设置。甚至还有一些小的帮助信息作为工具提示。

步骤

  1. 启动应用程序,如果需要,请重命名根文件夹。
  2. 点击“同步播放列表”按钮。
  3. 等待几秒钟开始操作。您可以看到 iTunes 正在启动,并且在指定的播放列表文件夹下创建新的文件夹和播放列表。
  4. 如果您想中止操作,可以随时进行。到目前为止的进度将保存在 iTunes 根目录下的一个工作文件夹中。
  5. 如果您希望保留备份,它将保存在备份文件夹中,但下次同步 iPod 时它将被更新。

Using the Code

整个开发周期我们将分四个阶段进行。

  1. 模拟 iTunes 文件夹子系统的设计。
  2. iTunes 接口系统的设计。
  3. 用户界面的设计。
  4. 性能优化和用户体验改进。

1) 模拟 iTunes 文件夹子系统的设计 - 模型

当我开始研究 iTunes 的架构时,我发现 iTunes 的子系统不是按照我需要的树状结构来设计的。至少它没有通过 API 暴露出来(或者更糟的是,我无法找出如何使用?)。每一步我都需要从集合中查找元素,而且我没有看到一个可行的解决方案来主要处理文件夹名称,这可能会导致重复。为了解决这个问题,我需要在我的应用程序中创建我自己的树状子系统。

设计从 iTunes 子系统中提取了三类树元素,它们本质上是 iTunes 中的一个基类。

a) 文件夹,b) 播放列表,c) 曲目。

这是用于此目的的代码。

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) iTunes 接口系统的设计

完成模拟文件夹系统的设计后,我们应该提供一个接口来执行与 iTunes 通信的功能。以下是执行这些操作的类。

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) 用户界面的设计

最后,在考虑 UI 界面设计时,发现使用 MVC 模式是合适的,因为 UI 功能似乎变得复杂。控制器包含了 iTunes 接口和模拟文件夹系统之间的集成逻辑,并最终将输出提供给 UI。这一部分由三个组件组成:仪表盘代码隐藏、仪表盘控制器以及最终的仪表盘 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 < numTracks; 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)
                {
                    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)));
                    }
                }
            }
            //optionaly deletes the missing tracks.
            foreach (var item in trackstodelete)
            {
                item.Delete();
            }
            //deletes the unrequired subfolders
            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();
            //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 < 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) 性能优化和用户体验改进

好的。最后我们编译并运行了应用程序。如果您尝试运行该应用程序,您会看到应用程序在 iTunes 中创建的子文件夹与您硬盘上的歌曲文件夹结构相同。显然,您需要配置 iTunes 不要将所有添加的文件复制到 iTunes 文件夹,因为那样您将无法控制文件的位置。这样您就可以将大量音乐装进口袋,并更好地控制您的音乐文件(至少对我来说效果很好)。

应用程序已正常运行并创建了播放列表。但我希望对一些性能进行调整,并在应用程序中添加一些用户体验项。

1) 多线程组件 - 防止 UI 在长时间操作期间冻结

我们可以在 iTunes 中看到输出,但是,我们的应用程序在操作完成之前一直没有响应。这是因为 UI 和后台操作在同一个线程上完成,所以我们需要将 iTunes 操作从主线程移开。因此,我们创建了另一个线程。

第 1 部分)

这段代码可以在 InteractionUtils 类中找到。这个扩展方法附加到 Dispatcher 对象上,我们可以将 Lambda 表达式或 Action 实例传递给它,以便在主线程上执行操作。

        public static void RemoteThreadUpdate(this Dispatcher ctrlControl,
            Action mtdLambadaExpression)
        {
            if (ctrlControl.CheckAccess())
            {
                mtdLambadaExpression();
            }
            else
            {
                ctrlControl.BeginInvoke(DispatcherPriority.Normal, mtdLambadaExpression);
            }
        }

第 2 部分)

DashBorad.xaml.csbtnsyncplaylist_Click(object sender, RoutedEventArgs e) 中实例化了一个新线程。

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

用于将数据传递到业务层的相关重载,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));
        }

第 3 部分)

使用进度和操作日志更新 UI 需要从子线程传递消息。这可以通过 DashBoardController.cs 中的一些事件来实现。

        public event EventHandler<DataEventArgs<CreateEventArgs>> ProgressStatus;
 
        public event EventHandler<DataEventArgs<double>> BeforeCreate;
 
        public event EventHandler<DataEventArgs<bool>> AfterCreate;

这些事件在 Dashboard.xaml.cs 中被订阅。

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

2) 循环优化

第 1 部分)

iTunes 不提供机制来查找存在于特定文件夹中的播放列表/文件夹/曲目。iTunes SDK 提供了一些有限的搜索功能,但要以高性能的方式实现此功能,我们需要创建自己的搜索算法。文件夹系统的创建解决了这个问题。(最初我在研究 iTunes SDK 时注意到此功能缺失,并决定创建一个树状图系统,以便应用程序能够上下导航树)。

第 2 部分)

我们还有一些改进要做。如果您调试曲目列表(LibraryPlaylist.Tracks),您会发现在大多数情况下,曲目是连续排列的,并且每个文件夹可以包含多个曲目。因此,如果当前曲目与最后一个曲目在同一个播放列表中,就没有必要从根到叶进行导航。

这可以通过简单的位置检查来实现,

        if (lastlocation == currentlocation)

此优化在运行应用程序时会产生明显的效果。在过去,当定位到一个新文件夹时,您会看到输出中出现短暂的停顿。

3) 可选移除缺失的曲目

在方法中

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

您可以看到这段代码,它会移除 Location 为 null 的曲目。我知道 iTunes Somewhere 存储了原始位置,但我无法找到。可能它没有通过 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) 可选从根目录移除空文件夹

如果主音乐文件夹从 iTunes 音乐文件夹的子文件夹中的某个地方开始,此功能就很有用。我相信在导航 iTunes 时,人们不想通过一系列空的播放列表文件夹来到达某个子文件夹。您可以在方法中找到这段代码。

        private void RemoveEmptyFolders(bool removeEmptyFolders)

5) 正确断开 iTunes 连接

即使在实用程序终止后,iTunes 实例仍将被锁定。这是因为 .NET 即使在应用程序终止后也无法释放 COM 资源。因此,我们需要显式地释放它们。

        Application.Current.Exit+=new ExitEventHandler((x, y) =>
        {
        ItnuesApp.Disconnect();
        ItnuesPlayListManager.Properties.Settings.Default.Save();
        });

异常

  1. 该工具仅考虑物理路径可以找到的曲目。通常,如果您打开 iTunes 并看到曲目旁边有一个感叹号(!),那些曲目将不会被添加到自动生成的播放列表中。我正在努力解决这个问题,您可以期待未来的版本修复此问题。
  2. 建议您在 iTunes 中设置选项,使其不会将添加到库中的所有文件复制到 iTunes 的本地文件夹。少量文件是可以的,例如经过转换的文件。过多的文件将导致与 iTunes 默认功能相同的情况,即在名为“我的音乐”的一个文件夹中有过多的播放列表。

系统要求

已知该软件可在以下配置下工作

  • Microsoft Windows XP SP1/Vista/Windows7
  • Microsoft .NET Framework 3.5
  • Apple iTunes 9.0 或更高版本

该软件可能在不同的配置下工作,但截至目前尚未得到验证。

结论

我希望这篇文章能帮助那些想要一个小型工具来摆脱在苹果音乐设备中查找音乐的痛苦的人。虽然在 MAC 平台上有很多这类工具,但我还没有看到很多 Windows 平台上的 iTunes 接口。我想给那些想要入门示例的人一个机会。请在下面的论坛中添加评论、建议和改进。

© . All rights reserved.