iTunesLibで遊ぶ in C# コード晒します(羞恥心)
仕事で東京に行っていたので、ずいぶん間が空いてしまいました。
世に出回っているものに比べ、まだいろいろ貧弱ですが、とりあえず形になりましたので、
前回宣言したとおり「iTunesLibで遊ぶ in C#」のコード晒します(一部省略)。
あまり見所といった見所はありませんが、
しいて言うなら、歌詞検索サイトから歌詞の取得を行っているところが見所でしょうか
あとは、その歌詞取得処理を非同期で行っている点とか・・。それではどうぞ。
iTunesAppClassをラップしたiTunesContorlクラス
まずは、iTunesAppClassをラップしたクラス、iTunesContorlを作成します。
iTunesAppClassはCOMオブジェクトなので、とりあえずラップしておきましょうってゆー。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using iTunesLib; using System.Windows.Forms; namespace iTunesLibrary { public class iTunesContorl : IDisposable { #region メンバ /// <summary> /// Disposeされたかどうか /// </summary> private bool disposed; /// <summary> /// UIスレッドで処理を行うためのWindow /// </summary> private Form _w; /// <summary> /// iTunesApplication COMオブジェクト /// </summary> private static iTunesAppClass _iTunesApp = new iTunesAppClass(); #endregion #region イベント /// <summary> /// 曲が再生されたときのイベント /// </summary> public event _IiTunesEvents_OnPlayerPlayEventEventHandler OnPlayerPlayEvent; #endregion #region コンストラクタ /// <summary> /// コンストラクタ /// </summary> public iTunesContorl(Form w) { //Disposeされていませんよ this.disposed = false; //UI _w = w; // _iTunesApp.OnPlayerPlayEvent += new _IiTunesEvents_OnPlayerPlayEventEventHandler(iTunesApp_OnPlayerPlayEvent); } #endregion #region デストラクタ /// <summary> /// デストラクタ /// </summary> ~iTunesContorl() { //アンマネージリソースの解放 this.Dispose(false); } #endregion #region プロパティ /// <summary> /// iTunesAppを取得します。 /// </summary> public iTunesApp iTunes { get { return _iTunesApp; } } #endregion #region IDisposable メンバ /// <summary> /// Dispose /// </summary> public void Dispose() { //マネージリソースおよびアンマネージリソースの解放 this.Dispose(true); //ガベコレから、このオブジェクトのデストラクタを対象外とする GC.SuppressFinalize(this); } #endregion #region メソッド #region IDisposable関連 /// <summary> /// Dispose /// シールクラスの場合ははprivateメソッドとする /// </summary> /// <param name="disposing"></param> protected virtual void Dispose(bool disposing) { lock (this) { if (this.disposed) { //既に呼びだしずみであるならばなんもしない return; } this.disposed = true; if (disposing) { // マネージリソースの解放をここに書く } // アンマネージリソースの解放 MarshalReleaseComObject(_iTunesApp); } } /// <summary> /// Dispose済みの場合ObjectDisposedExceptionをthrow /// クラスがシールクラスの場合ははprivateメソッドとする /// </summary> protected void ThrowExceptionIfDisposed() { if (this.disposed) { throw new ObjectDisposedException(this.GetType().ToString()); } } /// <summary> /// COMオブジェクトの参照カウントが0になるまで /// Marshal.ReleaceComObjectによるCOMオブジェクトの解放処理を行います。 /// </summary> /// <param name="objCom">COMオブジェクト</param> private void MarshalReleaseComObject(object objCom) { try { int i = 1; if (objCom != null && System.Runtime.InteropServices.Marshal.IsComObject(objCom)) { do { //ランタイム呼び出し可能ラッパーの参照カウントをデクリメント i = System.Runtime.InteropServices.Marshal.ReleaseComObject(objCom); } while (i > 0); } } finally { objCom = null; } } #endregion #region iTunes関連 /// <summary> /// 曲が再生されたとき /// </summary> private void iTunesApp_OnPlayerPlayEvent(object iTrack) { //すでにDispose済みの場合は例外throw this.ThrowExceptionIfDisposed(); _w.Invoke(OnPlayerPlayEvent, new object[] { iTrack }); } #endregion #endregion } }
歌詞取得処理の実装
このアプリのメイン機能である歌詞取得処理の実装をします。
インターフェイスの抽出はもっと洗練できるのはわかっているんだけど、今回はこの程度で。
歌詞取得のインターフェイス
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace iTunely.LyricGetter { /// <summary> /// 歌詞取得クラスが実装するインターフェイス /// </summary> public interface ILyricGetter { /// <summary> /// 対象のhttp URLを取得します。 /// </summary> string http { get; } /// <summary> /// 歌詞を取得します。 /// </summary> string GetLyric(); } }
そのインターフェイスを実装して、抽象歌詞取得クラスつくっちゃいます。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using iTunesLib; namespace iTunely.LyricGetter { /// <summary> /// 抽象歌詞取得クラス /// </summary> public abstract class AbstractLyricGetter : ILyricGetter { #region メンバ /// <summary> /// トラック情報 /// </summary> protected IITTrack _track; /// <summary> /// エンコーディング /// </summary> protected static readonly Encoding enco = Encoding.GetEncoding("Shift_JIS"); #endregion /// <summary> /// コンストラクタ /// </summary> /// <param name="track"></param> protected AbstractLyricGetter(IITTrack track) { this._track = track; } #region ILyricGetter メンバ /// <summary> /// 対象のhttp URLを取得します。 /// </summary> abstract public string http { get;} /// <summary> /// 歌詞を取得します。 /// </summary> /// <returns>歌詞</returns> public virtual string GetLyric() { //iTunesに歌詞が登録済みであれば、そっちから取得 var lyric = ((IITFileOrCDTrack)this._track).Lyrics; if (!string.IsNullOrEmpty(lyric)) { return lyric; } //Webに接続されているかどうかを判定 int flags; if (!Utility.InternetGetConnectedState(out flags, 0)) return "インターネットに接続されていません。\r\n歌詞を取得できませんでした(^ω^;)"; if (!Utility.IsInternetConnected()) return "インターネットに接続されていません。\r\n歌詞を取得できませんでした(^ω^;)"; return lyric; } #endregion } }
んで、具象クラスを作ります。今回は「うたまっぷ」から歌詞をもらってくるクラスを作ります。
抽象歌詞取得クラスから、「歌ネット」等の他の歌詞検索サイトから取得する具象クラスも同じ感じで作れますね。
HTMLの解析に正規表現を使っているのがわかると思います。
文字列操作はやっぱ正規表現だね。あらためて感じる正規表現の素晴らしさw
using System; using System.IO; using System.Net; using System.Web; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using Microsoft.VisualBasic; using iTunesLib; namespace iTunely.LyricGetter { /// <summary> /// 歌詞取得クラス(うたまっぷ) /// </summary> public class UtamapLyricGetter : AbstractLyricGetter { #region コンストラクタ /// <summary> /// コンストラクタ /// </summary> /// <param name="track"></param> public UtamapLyricGetter(IITTrack track): base(track) {} #endregion #region プロパティ /// <summary> /// うたまっぷのURLを取得します。 /// </summary> public override string http { get { return "http://www.utamap.com/"; } } #endregion #region メソッド /// <summary> /// 歌詞を取得します。 /// </summary> /// <returns>歌詞</returns> public override string GetLyric() { var lyric = base.GetLyric(); if ( lyric != null ) return lyric; //アーティスト名をURLエンコ var artist = HttpUtility.UrlEncode(this._track.Artist, enco); var url = http + "searchkasi.php?searchname=artist&word=" + artist + "&act=search&sortname=1&pattern=3"; //歌詞を取得 lyric = SearchAllPageHtml(url, enco); if (lyric == null) return null; //iTunesで対象のTrackに歌詞を登録 ((IITFileOrCDTrack)this._track).Lyrics = lyric; return lyric; } /// <summary> /// 対象URL検索結果全ページから、対象Trackの歌詞を検索します。 /// </summary> /// <param name="url">URL</param> /// <param name="enco">encode</param> /// <returns></returns> private string SearchAllPageHtml(string url, Encoding enco) { var html = GetHtml(url, enco); if (html == "") return null; var trackName = ComparisonTrackName(this._track.Name); var trackID = GetTrackID(html, trackName); if (trackID != null) { return GetLyric(trackID); } //TODO キャッシュとか使ってやった方が効率よさげだけどめんどいので今はやんない //次のページ分のURLを取得 var reg = new Regex(@"(searchkasi.php\?page=[^>]*)(?:"">次)"); var m = reg.Match(html); //最後のページであれば抜ける if (m.ToString() == "") return null; return SearchAllPageHtml(http + m.Groups[1].Value, enco); } /// <summary> /// 対象HTMLからTrackIDを取得します。 /// </summary> /// <param name="html">対象HTML</param> /// <param name="trackName">曲名</param> /// <returns>TrackID</returns> private string GetTrackID(string html, string trackName) { //曲名およびURLを含む部分を取得 var reg = new Regex(@"(?:./showkasi.php\?surl=)(.*)(?:"">)(.*)(?:</A>)"); foreach (Match m in reg.Matches(html)) { if (m.Success) { var tn = m.Groups[2].Value; if (tn == "") continue; //比較用曲名へ変換 tn = ComparisonTrackName(tn); if (tn == trackName) return m.Groups[1].Value; } } return null; } /// <summary> /// 比較用に編集したTrackNameを取得します。 /// </summary> /// <param name="trackName">曲名</param> /// <returns>比較用曲名</returns> private string ComparisonTrackName(string trackName) { //大文字へ変換 trackName = trackName.ToUpper(); //TODO ヒマがあったらStrConvではなくて、APIのLCMapString関数あたりを使うようにしたりとか //ひらがなをカタカナへ trackName = Strings.StrConv(trackName, VbStrConv.Katakana, 0); //半角を全角へ trackName = Strings.StrConv(trackName, VbStrConv.Wide, 0); return trackName; } /// <summary> /// 対象URLのHTMLを取得します。 /// 取得できない場合は、nullを返します。 /// </summary> /// <param name="url">対象のURL</param> /// <param name="enco">encode</param> /// <returns></returns> private string GetHtml(string url, Encoding enco) { var req = WebRequest.Create(url) as HttpWebRequest; HttpWebResponse res = null; try { res = req.GetResponse() as HttpWebResponse; } catch { return null; } using (var reader = new StreamReader(res.GetResponseStream(), enco)) { var html = reader.ReadToEnd(); reader.Close(); //取得したHTML return html; } } /// <summary> /// 対象歌詞IDの歌詞を取得します。 /// </summary> /// <param name="lyricId">歌詞ID</param> /// <returns>歌詞</returns> private string GetLyric(string lyricId) { var url = http + "phpflash/flashfalsephp.php?unum=" + lyricId; //HTMLを取得 var html = GetHtml(url, Encoding.UTF8); var reg = new Regex(@"(?:test.*=)(.*)", RegexOptions.Singleline); return reg.Match(html).Groups[1].Value.Replace("\n", "\r\n"); } #endregion } }
画面UIの実装
続きまして、画面UI部分の実装をやっちゃいましょう。
using System; using System.IO; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Text; using System.Collections.Generic; using System.ComponentModel; using System.Windows.Forms; using System.Text.RegularExpressions; using System.Threading; using iTunesLib; using iTunesLibrary; using iTunely.LyricGetter; using iTunely.Control; namespace iTunely { /// <summary> /// iTunelyForm /// </summary> public partial class iTunelyForm : Form { #region メンバ /// <summary> /// iTunesControl /// </summary> private static iTunesContorl _itunes; #endregion #region コンストラクタ /// <summary> /// デフォルトコンストラクタ /// </summary> public iTunelyForm() { InitializeComponent(); //初期処理 Init(); } #endregion #region イベント private ToolStripCheckBox tschkTopMost; /// <summary> /// iTunesの曲が再生されたときのイベントで /// </summary> /// <param name="iTrack"></param> private void iTunesApp_OnPlayerPlayEvent(object iTrack) { ChangeButtonCaption(); //曲名とか設定 var track = ((IITTrack)iTrack); this.tslblTrackName.Text = String.Format("{0} - {1} - {2}", track.Artist, track.Name, track.Album); //アートワークの設定 SetArtWork(track); //歌詞の取得 ILyricGetter lg = CreateLyricGetter(track); var caller = new Func<string>(lg.GetLyric); Utility.SetText("", this.txtLyric); //ローディング表示を開始 Utility.SetVisible(true, this.picLoading); //非同期で歌詞を取得 var result = caller.BeginInvoke(new AsyncCallback(GetLyricCallback), caller); } /// <summary> /// 次の曲 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void tsbtnNextTrack_Click(object sender, EventArgs e) { _itunes.iTunes.NextTrack(); } /// <summary> /// 再生/停止 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void tsbtnPlayPause_Click(object sender, EventArgs e) { _itunes.iTunes.PlayPause(); ChangeButtonCaption(); } /// <summary> /// 前の曲 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void tsbtnPrevTrack_Click(object sender, EventArgs e) { _itunes.iTunes.PreviousTrack(); } /// <summary> /// 常に手前表示 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void chkTopMost_CheckedChanged(object sender, EventArgs e) { this.TopMost = this.tschkTopMost.Checked; } /// <summary> /// GetLyricCallback /// </summary> /// <param name="ar"></param> private void GetLyricCallback(IAsyncResult ar) { var caller = (Func<string>)ar.AsyncState; var returnValue = caller.EndInvoke(ar); //取得した歌詞を設定 Utility.SetText(returnValue ?? "取得できませんでした(^ω^;)", this.txtLyric); //ローディング表示を戻す Utility.SetVisible(false, this.picLoading); } #endregion #region メソッド /// <summary> /// 初期処理 /// </summary> private void Init() { _itunes = new iTunesLibrary.iTunesContorl(this); _itunes.OnPlayerPlayEvent += new _IiTunesEvents_OnPlayerPlayEventEventHandler(iTunesApp_OnPlayerPlayEvent); this.pctMusicImage.Image = Properties.Resources.no_artwork; this.picLoading.Visible = false; ChangeButtonCaption(); this.toolStrip1.Items.Add(this.tslblTrackName); this.toolStrip1.Items.Add(this.tsbtnPrevTrack); this.toolStrip1.Items.Add(this.tsbtnPlayPause); this.toolStrip1.Items.Add(this.tsbtnNextTrack); this.tsbtnPrevTrack.Click += new EventHandler(tsbtnPrevTrack_Click); this.tsbtnPlayPause.Click += new EventHandler(tsbtnPlayPause_Click); this.tsbtnNextTrack.Click += new EventHandler(tsbtnNextTrack_Click); //ToolStripCheckBoxの追加 AddToolStripCheckBox(); } /// <summary> /// ToolStripCheckBoxを追加します。 /// </summary> private void AddToolStripCheckBox() { //ToolStripCheckBoxの作成 tschkTopMost = new ToolStripCheckBox(); tschkTopMost.CheckBoxText = "常に手前"; //tschkTopMostの値を変更 tschkTopMost.Checked = true; this.TopMost = tschkTopMost.Checked; //CheckChangedイベントハンドラの追加 tschkTopMost.CheckedChanged += new EventHandler(chkTopMost_CheckedChanged); tschkTopMost.Alignment = ToolStripItemAlignment.Right; //toolStrip1に追加 toolStrip1.Items.Add(tschkTopMost); } /// <summary> /// ILyricGetterインターフェイスを実装した何かを取得しますよ。 /// </summary> /// <returns></returns> private ILyricGetter CreateLyricGetter(IITTrack track) { //いまのところ「うたまっぷ」からの取得しか作ってないっす return new UtamapLyricGetter(track); } /// <summary> /// ボタンのText切替 /// </summary> private void ChangeButtonCaption() { if (_itunes.iTunes.PlayerState == ITPlayerState.ITPlayerStatePlaying) { tsbtnPlayPause.Text = "停止"; tsbtnPlayPause.Image = Properties.Resources.audio_pause; return; } tsbtnPlayPause.Text = "再生"; tsbtnPlayPause.Image = Properties.Resources.audio_play; } /// <summary> /// アートワークを設定します。 /// </summary> private void SetArtWork(IITTrack track) { var awc = from IITArtwork aw in track.Artwork select aw; if (awc.Count() == 0) { this.pctMusicImage.Image = Properties.Resources.no_artwork; return; } foreach (var aw in awc) { var tmpFileName = @"c:\iTunesPhoto\tmp.jpg"; aw.SaveArtworkToFile(tmpFileName); using (var fs = new FileStream(tmpFileName, FileMode.Open, FileAccess.Read)) { this.pctMusicImage.Image = Image.FromStream(fs); fs.Close(); } break; } } #endregion /// <summary> /// OnPaint /// </summary> /// <param name="e"></param> protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); var g = e.Graphics; //g.VisibleClipBoundsは表示クリッピング領域に外接する四角 LinearGradientBrush gb = new LinearGradientBrush( g.VisibleClipBounds, Color.White,Color.Gray, LinearGradientMode.Vertical); g.FillRectangle(gb, g.VisibleClipBounds); } } }
デフォルトではToolStripCheckBoxってのが用意されていないので、
ToolStripControlHostを継承して自作しちゃいます。何も深いこと考えなくてよくて簡単ですね。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Windows.Forms; namespace iTunely.Control { /// <summary> /// ToolStripCheckBox /// </summary> [ToolboxBitmap(typeof(CheckBox),"CheckBox")] public partial class ToolStripCheckBox : ToolStripControlHost { #region コンストラクタ /// <summary> /// コンストラクタ /// </summary> public ToolStripCheckBox() : base(new CheckBox()) { InitializeComponent(); this.BackColor = Color.Transparent; } #endregion #region プロパティ /// <summary> /// ホストしているCheckBoxコントロール /// </summary> public CheckBox CheckBox { get { return (CheckBox)Control; } } /// <summary> /// ホストしているコントロールのCheckedの値を取得または設定します。 /// </summary> public bool Checked { get { return CheckBox.Checked; } set { CheckBox.Checked = value; } } /// <summary> /// ホストしているコントロールのTextの値を取得または設定します。 /// </summary> public string CheckBoxText { get { return CheckBox.Text; } set { CheckBox.Text = value; } } #endregion #region イベント /// <summary> /// Checkedの値が変化した /// </summary> public event EventHandler CheckedChanged; /// <summary> /// CheckedChangedイベント発生 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void CheckBox_OnCheckedChanged(object sender, EventArgs e) { if (CheckedChanged != null) { CheckedChanged(this, e); } } #endregion #region オーバーライド /// <summary> /// ホストしているCheckBoxのイベントをサブスクライブします。 /// </summary> /// <param name="control"></param> protected override void OnSubscribeControlEvents(System.Windows.Forms.Control control) { base.OnSubscribeControlEvents(control); CheckBox chkControl = (CheckBox)control; chkControl.CheckedChanged += new EventHandler(CheckBox_OnCheckedChanged); } /// <summary> /// ホストしているCheckBoxのイベントをアンサブスクライブします。 /// </summary> /// <param name="control"></param> protected override void OnUnsubscribeControlEvents(System.Windows.Forms.Control control) { base.OnUnsubscribeControlEvents(control); CheckBox chkControl = (CheckBox)control; chkControl.CheckedChanged -= new EventHandler(CheckBox_OnCheckedChanged); } #endregion } }
ユーティリティ的なもの
インターネット接続チェックとか一応実装しておきました。
(constのINTERNET_CONNECTION_MODEMとかは、今回は一切つかってません)
あと、非同期処理からテキストを設定するには、スレッドセーフにコントロールを扱えるように
せにゃらなんのですよね。せっかくなのでジェネリクスで適当に汎化しちゃいました。
using System; using System.Windows.Forms; using System.Collections.Generic; using System.Linq; using System.Text; namespace iTunely { public static class Utility { #region DllImport /// <summary> /// InternetGetConnectedState /// ローカルシステムの接続状態を取得します。 /// </summary> /// <param name="lpdwFlags"> /// 接続状態 /// INTERNET_CONNECTION_MODEM /// INTERNET_CONNECTION_LAN /// INTERNET_CONNECTION_PROXY /// INTERNET_RAS_INSTALLED /// INTERNET_CONNECTION_OFFLINE /// INTERNET_CONNECTION_CONFIGURED /// </param> /// <param name="dwReserved">今後使用するために予約されてるっぽい</param> /// <returns></returns> [System.Runtime.InteropServices.DllImport("wininet.dll")] public extern static bool InternetGetConnectedState(out int lpdwFlags, int dwReserved); public const int INTERNET_CONNECTION_MODEM = 0x1; public const int INTERNET_CONNECTION_LAN = 0x2; public const int INTERNET_CONNECTION_PROXY = 0x4; public const int INTERNET_RAS_INSTALLED = 0x10; public const int INTERNET_CONNECTION_OFFLINE = 0x20; public const int INTERNET_CONNECTION_CONFIGURED = 0x40; #endregion /// <summary> /// インターネットに接続されているか否かを取得します。 /// </summary> /// <returns> /// true : 接続されている /// false : 接続されていない /// </returns> public static bool IsInternetConnected() { //インターネットに接続されているか確認する。というか、実際にping飛ばす var host = "http://www.google.com/"; System.Net.HttpWebRequest webreq = null; System.Net.HttpWebResponse webres = null; try { //HttpWebRequestの作成 webreq = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(host); //メソッドをHEADにする webreq.Method = "HEAD"; //受信する webres = (System.Net.HttpWebResponse)webreq.GetResponse(); return true; } catch { return false; } finally { if (webres != null) webres.Close(); } } /// <summary> /// 対象のコントロールのTextに文字列を設定します。(スレッドセーフ) /// </summary> /// <typeparam name="T">対象の型</typeparam> /// <param name="text">設定する文字列</param> /// <param name="control">対象のコントロール</param> public static void SetText<T>(string text, T control) where T : System.Windows.Forms.Control { if (control.InvokeRequired) { control.Invoke(new Action<string, T>(SetText), new object[] { text, control }); return; } control.Text = text; } /// <summary> /// 対象のコントロールのVisibleを設定します(スレッドセーフ) /// </summary> /// <param name="value">true,false</param> /// <param name="control">対象のコントロール</param> public static void SetVisible<T>(bool value, T control) where T : System.Windows.Forms.Control { if (control.InvokeRequired) { control.Invoke(new Action<bool, T>(SetVisible), new object[] { value, control }); return; } control.Visible = value; } } }
コードは以上です。
起動部分やデザイナ部分など、どーでもよさげなところは省略しました。著作権の放棄はしませんが、
載せたコードはご自由に改変などしてご利用くださって結構です。お疲れさまでした。
時間があるとき、ジョジョに奇妙なバージョンアップをしようと思います。
ゆくゆくは、ちゃんとWPFとか勉強してカッコイイUIにしたいなあ、とか。
んでは、明日からまた東京いってきます^^;