普段の音楽プレーヤーはiTunesだけどスマホはXperiaとかを使っていて、音楽データを他のソフトにエクスポートするような人は気付くと思うんですが、iTunesで自動的に取得されるアートワークって曲自体には埋め込まれていないんですよね。 で、エクスポートしたり他のソフトで再生しようとしたときにアートワークが表示されなくて、せっかくiTunes上では格好良くアートワークが並んだ状態なのにどこか残念さを感じたりするわけです。

まぁもともとiTunesから拾えるアートワークなんてたかが知れてる(J-POPは少々弱い)ので、Windows使いならCDをリッピングする度にBroadwayとかを使ってネット上のファイルからアートワークを追加してしまえば、iTunesでも曲自体にアートワークを埋め込むことができるんですが、とはいえもともと取ってこれる画像だってそこそこ綺麗だし、ひょっとするとネット検索で出てくるアートワークよりも高画質なことだってあるわけです。

というわけで、Windows版iTunesは外部アプリからプレーヤを制御したり楽曲情報を取得することができるiTunes COM SDKが存在するので、これを使ってiTunesを制御するついでにアートワークの埋め込みについてもチャレンジしてみました。

プロジェクト設定

iTunes COM SDK自体はCOMなので、VBScript経由で叩くことも、今回のようにC++やC#から叩くこともできます。昔の記事を見るとAppleからSDKを落としてきて云々と書かれている記事が多いですが、こういうことをする人のマシンには既にiTunesが入っているでしょうから、

  1. VisualStudioからソリューションエクスプローラーの「参照設定」を右クリック
  2. 参照の追加を選択
  3. 「COM」タブをクリックすると現れるコンポーネント名一覧から「iTunes 1.13 Type Library」を選択
  4. 使用するソースコードで「using iTunesLib;」を宣言

するだけでSDKが使えます。超お手軽。

サンプルアプリ

今回作成したWindows Formアプリのソースコードを抜粋して紹介します。

    public partial class Form1 : Form
    {
        private iTunesApp iTunes;
        System.Threading.Timer GUITimer;
        TimerCallback GUITimerCallback;

        string AlbumName, TrackInfo, Artist, TrackName;
        string ArtworkPath;
        IITArtwork artwork;

        public Form1()
        {
            InitializeComponent();

            //iTunes COMのセットアップ
            iTunes = new iTunesApp();
            iTunes.OnAboutToPromptUserToQuitEvent += new _IiTunesEvents_OnAboutToPromptUserToQuitEventEventHandler(iTunes_OnAboutToPromptUserToQuitEvent);
            iTunes.OnPlayerPlayEvent += new _IiTunesEvents_OnPlayerPlayEventEventHandler(iTunes_OnPlayerPlayEvent);

            //GUIへの反映(1秒おき)
            GUITimerCallback = new TimerCallback(updateGUI);
            GUITimer = new System.Threading.Timer(GUITimerCallback, null, 2000, 1000);
        }

        //GUIへのラベルの反映(やっつけ)
        void updateGUI(Object o)
        {
            BeginInvoke((MethodInvoker)delegate
            {
                this.AlbumNameLabel.Text = AlbumName;
                this.TrackNumberLabel.Text = TrackInfo;
                this.ArtistLabel.Text = Artist;
                this.TitleLabel.Text = TrackName;
            });
        }

        //再生イベント時に呼び出されるメソッド
        void iTunes_OnPlayerPlayEvent(object iTrack)
        {
            //再生中のトラック情報を取得
            IITTrack track = (IITTrack)iTrack;

            //ラベル用テキストを設定
            AlbumName = track.Album;
            TrackInfo = track.TrackNumber + " / " + track.TrackCount;
            Artist = track.Artist;
            TrackName = track.Name;

            //アートワークコレクションを取得
            IITArtworkCollection artwork = track.Artwork;

            if (artwork != null && artwork.Count != 0)
            {
                int artworkCount = 0;
                foreach (IITArtwork a in artwork)
                {
                    //ぶっちゃけアートワークは1個でいいので先頭だけを使用
                    artworkCount++;
                    if (artworkCount > 1) break;

                    //フォーマットに応じて保存先パスの拡張子を変更
                    if(a.Format == ITArtworkFormat.ITArtworkFormatJPEG)
                        ArtworkPath = Environment.CurrentDirectory + "\\artwork.jpg";
                    else if(a.Format == ITArtworkFormat.ITArtworkFormatBMP)
                        ArtworkPath = Environment.CurrentDirectory + "\\artwork.bmp";
                    else if (a.Format == ITArtworkFormat.ITArtworkFormatPNG)
                        ArtworkPath = Environment.CurrentDirectory + "\\artwork.png";
                    else //フォーマット不明であればこの後の処理は行わない(稀)
                    {
                        ArtworkPath = null;
                        break;
                    }

                    //アートワークを取得して保存
                    a.SaveArtworkToFile(ArtworkPath);

                    //保存したアートワークを取得(ファイルに落とさないと取れないから)
                    Bitmap AlbumArtContent = new Bitmap(ArtworkPath);

                    //スケールを計算
                    float scale_w = ((float)AlbumArtContent.Size.Width / ArtworkImageBox.Width), scale_h = ((float)AlbumArtContent.Size.Height / ArtworkImageBox.Height);
                    float scale = (scale_w > scale_h) ? (1 / scale_w) : (1 / scale_h);

                    //描画用に大きい方のスケールに合わせてリサイズ
                    Bitmap canvas = new Bitmap(ArtworkImageBox.Width, ArtworkImageBox.Height);
                    Graphics g = Graphics.FromImage(canvas);
                    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                    g.DrawImage(AlbumArtContent, 0, 0, (AlbumArtContent.Size.Width * scale), (AlbumArtContent.Size.Height * scale));
                    ArtworkImageBox.Image = canvas;

                    //もしiTunesから取ったアートワークであれば
                    //落とし込んだアートワークを埋め込むボタンを許可する
                    if (a.IsDownloadedArtwork)
                    {
                        BeginInvoke((MethodInvoker)delegate
                        {
                            this.EmbedArtworkInfoLabel.Text = "Embed Artwork?";
                            this.EmbedArtworkButton.Enabled = true;
                            this.artwork = a;
                        });
                    }
                    else
                    {
                        BeginInvoke((MethodInvoker)delegate
                        {
                            this.EmbedArtworkInfoLabel.Text = "Artwork has been embedded.";
                            this.EmbedArtworkButton.Enabled = false;
                            this.artwork = null;
                        });
                    }

                    //アルバムアートのビットマップを破棄
                    AlbumArtContent.Dispose();
                }
            }
            //アートワークが存在しない場合はアートワークが存在しない旨を表示
            else
            {
                Bitmap canvas = new Bitmap(ArtworkImageBox.Width, ArtworkImageBox.Height);
                Graphics g = Graphics.FromImage(canvas);
                g.DrawString("No Artwork!", new Font(FontFamily.GenericSansSerif, 14, GraphicsUnit.Point), Brushes.Black, 75, 111);
                ArtworkImageBox.Image = canvas;

                //アートワーク埋め込みは許可しない
                BeginInvoke((MethodInvoker)delegate
                {
                    this.EmbedArtworkInfoLabel.Text = "Artwork Not found.";
                    this.EmbedArtworkButton.Enabled = false;
                    this.artwork = null;
                });
            }
        }

        //iTunes COM SDKを解放
        void ReleaseCOM()
        {
            iTunes.OnPlayerPlayEvent -= iTunes_OnPlayerPlayEvent;
            iTunes.OnAboutToPromptUserToQuitEvent -= iTunes_OnAboutToPromptUserToQuitEvent;

            Marshal.ReleaseComObject(iTunes);
            iTunes = null;
        }

        //iTunes終了時に呼ばれる?メソッド
        void iTunes_OnAboutToPromptUserToQuitEvent()
        {
            ReleaseCOM();
            this.Invoke((MethodInvoker)delegate()
            {
                this.Close();
            });
        }

        //フォーム終了時もCOMの解放を行う
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (iTunes != null) ReleaseCOM();
            GUITimer.Dispose();
        }

        private void PrevButton_Click(object sender, EventArgs e)
        {
            iTunes.PreviousTrack();
        }

        private void PlayPauseButton_Click(object sender, EventArgs e)
        {
            iTunes.PlayPause();
        }

        private void NextButton_Click(object sender, EventArgs e)
        {
            iTunes.NextTrack();
        }

        //ファイルからアートワークを上書きするボタン
        private void EmbedArtworkButton_Click(object sender, EventArgs e)
        {
            if (ArtworkPath == null || artwork == null) return;
            try
            {
                //ファイルのパスを書くだけでアートワークを上書きできる
                //正しいパスをセットしてこのメソッドを呼べば画像自体が曲データに埋め込まれるので
                //元の画像は削除されてもOK
                artwork.SetArtworkFromFile(ArtworkPath);
            }
            //書き込みに失敗した場合は例外が返ってくる
            catch (COMException com)
            {
                MessageBox.Show(com.Message);
                return;
            }

            MessageBox.Show("Set Artwork Successful!");

            //書き込み中は一時停止されるので、再生
            iTunes.Play();
        }
    }

画面描画系の処理がやっつけになっているのはサクッと試してみたかったせいだという点でご容赦頂きたいところですが、本質的な処理はiTunes COM SDKが比較的よくできているおかげで実装はかなりシンプルです。

気をつけるべき点としては、

といったところでしょうか。

アートワークの埋め込み

iTunes側では、Broadwayのように外部からファイルで設定されたアートワークについてはきちんと楽曲自体に埋め込まれます。 また、SDKには「アートワークがiTunesからダウンロードされたものであるかどうか」を示すフラグが用意されていますので、これをもとにアートワークが埋め込まれているかいないかを判定します。

先述の通り、アートワークはバイナリとして読み取るのではなくSDK側でファイルに書き込んでくれるので、これを使用します。パスは適当に変更してもよいのですが、ここでは実行ディレクトリ直下に保存し、埋め込みを行う場合はこの画像パスを渡すことで元のiTunesで取得されたアートワークを楽曲データに埋め込んでいます。

ちなみに、最近始まったiTunes MUSICの楽曲データについては、トラック名などの情報は取得でき、もちろんプレーヤー自体の制御もできるのですが、アートワークについては存在しない扱いになっているため取得できませんでした。まぁ、当たり前っちゃ当たり前ですね。

さいごに

今時こうやってiTunes SDKを触る人も珍しいと思いますが、これとmbedやArduinoを使用して自作のコントローラを作成したり、NowPlayingをつぶやくプラグインなどが作成できると思いますので、そういった場合の参考になれば幸いです。