第25回 曲情報をデータベースに保存する

前回「第24回 ファイル追加日時条件で再生スキップする」は再生条件のうち、ファイル追加日時の判断を追加しました。
このまま今回はアーティストレーティング/曲レーティングの条件判断処理を入れたいところですが、
曲名/アーティスト名の情報を随時ファイルから読み込む方式だと効率が悪いので、
先に曲情報をデータベースへ格納する処理を作成します。

データベースはSqlite3、ライブラリはSystem.Data.SQLitePublic Domain)を利用します。
Microsoft Entity FrameworkMICROSOFT SOFTWARE LICENSE)に依存関係が発生します。

SQLiteを扱うライブラリを取り込むため、
「プロジェクト」メニューにある「NuGetパッケージの管理」を選択して、オンラインで「sqlite」を検索。
ヒットした中から「System.Data.SQLite (x86/x64)」をインストールします。

そして実際にDBへ情報を保存する処理を追加します。
次に「プロジェクト」メニューのある「クラスの追加」を選択して「MP3Info.cs」を作成。
さらにForm1に関連処理を書いておきます。

これで起動時にDBデータがあるかどうかチェックし、DBデータがなければ再生フォルダ内の全音楽ファイルから情報を読み込んでDBへ保存します。
(音楽ファイルが多いと、ものすごく時間がかかります)
2度目以降の起動ではこの処理は行われません。

実際にDBから情報を読みだして利用する処理は次回以降へ持ち越し。

■Mp3Info.cs
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MP3Player
{
	class MP3Info
	{
		static SQLiteConnection _conn = null;

		const string _strDBFileName = "mp3info.sql";

		static MP3Info()
		{
			SQL_OpenConnection();
		}



		public static void Close()
		{
			SQL_CloseConnection();
		}


		/// <summary>
		/// DBを開き、テーブルを作成する
		/// </summary>
		static bool SQL_OpenConnection()
		{
			if (_conn != null)
				return true;

			try
			{
				//DBオープン
				_conn = new SQLiteConnection();
				_conn.ConnectionString = "Data Source=" + Utility.GetExeFolder() + _strDBFileName + ";Version=3;";
				_conn.Open();

				//テーブル作成(すでにテーブルがあっても気にせず実行)
				try
				{
					SQLiteCommand cmd = _conn.CreateCommand();
					cmd.CommandText = "CREATE TABLE TagInfo (id INTEGER primary key AUTOINCREMENT, file TEXT, lastwritedate INTEGER, entryindex INTEGER, title TEXT, artist TEXT, album TEXT)";
					cmd.ExecuteNonQuery();
				}
				catch (Exception)
				{
				}

				return true;
			}
			catch (Exception)
			{
			}

			_conn = null;
			return false;
		}


		/// <summary>
		/// DBを閉じる
		/// </summary>
		static void SQL_CloseConnection()
		{
			try
			{
				if (_conn != null)
					_conn.Close();
			}
			catch (Exception)
			{
			}
			_conn = null;
		}


		/// <summary>
		/// データ数の取得
		/// </summary>
		public static long GetCount()
		{
			try
			{
				using (SQLiteCommand cmd = _conn.CreateCommand())
				{
					cmd.CommandText = @"SELECT count(*) FROM TagInfo;";

					using (SQLiteDataReader reader = cmd.ExecuteReader())
					{
						while (reader.Read())
						{
							return reader.GetInt64(0);
						}
					}
				}
			}
			catch (Exception)
			{
			}
			return 0;
		}



		/// <summary>
		/// DBからの情報取得
		/// </summary>
		static bool SQL_FindTagInfo(AudioItem item, out DateTime dtLastWrite, out string strTitle, out string strArtist, out string strAlbum)
		{
			//引数で渡されたstrFileは音楽フォルダーのパスを含んだものかもしれないので、その部分を除外
			string strFile = Form1._option.RemoveMP3FolderName(item.strFile);

			try
			{
				using (SQLiteCommand cmd = _conn.CreateCommand())
				{
					cmd.CommandText = @"SELECT lastwritedate, title, artist, album FROM TagInfo WHERE file=@file and entryindex=@entryindex;";
					cmd.Parameters.Add(new SQLiteParameter("@file", strFile));
					cmd.Parameters.Add(new SQLiteParameter("@entryindex", item.nEntryIndex));
					cmd.Prepare();

					using (SQLiteDataReader reader = cmd.ExecuteReader())
					{
						while (reader.Read())
						{
							try
							{
								dtLastWrite = new DateTime(reader.GetInt64(0));
								strTitle = reader.GetString(1);
								strArtist = reader.GetString(2);
								strAlbum = reader.GetString(3);
								return true;
							}
							catch (Exception)
							{
							}
						}
					}
				}
			}
			catch (Exception)
			{
			}

			dtLastWrite = DateTime.MinValue;
			strTitle = "";
			strArtist = "";
			strAlbum = "";
			return false;
		}



		/// <summary>
		/// DBへのデータ保存
		/// </summary>
		static bool SQL_SetTagInfo(AudioItem item, DateTime dtLastWrite, string strTitle, string strArtist, string strAlbum)
		{
			//引数で渡されたstrFileは音楽フォルダーのパスを含んだものかもしれないので、その部分を除外
			string strFile = Form1._option.RemoveMP3FolderName(item.strFile);

			try
			{
				int nID = -1;

				//DB中にデータがすでにあるかどうかチェック。あればIDを取得
				using (SQLiteCommand cmd = _conn.CreateCommand())
				{
					cmd.CommandText = @"SELECT id FROM TagInfo WHERE file=@file and entryindex=@entryindex;";
					cmd.Parameters.Add(new SQLiteParameter("@file", strFile));
					cmd.Parameters.Add(new SQLiteParameter("@entryindex", item.nEntryIndex));
					cmd.Prepare();

					using (SQLiteDataReader reader = cmd.ExecuteReader())
					{
						while (reader.Read())
						{
							try
							{
								nID = reader.GetInt32(0);
								break;
							}
							catch (Exception)
							{
							}
						}
					}
				}

				//IDがなかった=データがない=新規追加
				if (nID < 0)
					return SQL_AddNewTagInfo(item, dtLastWrite, strTitle, strArtist, strAlbum);

				//IDが見つかった=データがある=更新
				return SQL_UpdateTagInfo(nID, dtLastWrite, strTitle, strArtist, strAlbum);
			}
			catch (Exception)
			{
			}

			return false;
		}



		/// <summary>
		/// DBへのデータ追加
		/// </summary>
		static bool SQL_AddNewTagInfo(AudioItem item, DateTime dtLastWrite, string strTitle, string strArtist, string strAlbum)
		{
			//引数で渡されたstrFileは音楽フォルダーのパスを含んだものかもしれないので、その部分を除外
			string strFile = Form1._option.RemoveMP3FolderName(item.strFile);

			try
			{
				int nRet = -1;
				using (SQLiteTransaction transaction = _conn.BeginTransaction())
				using (SQLiteCommand cmd = _conn.CreateCommand())
				{
					cmd.CommandText = @"INSERT INTO TagInfo (lastwritedate, file, entryindex, title, artist, album) VALUES(@lastwritedate, @file, @entryindex, @title, @artist, @album);";
					cmd.Parameters.Add(new SQLiteParameter("@lastwritedate", dtLastWrite.Ticks));
					cmd.Parameters.Add(new SQLiteParameter("@file", strFile));
					cmd.Parameters.Add(new SQLiteParameter("@entryindex", item.nEntryIndex));
					cmd.Parameters.Add(new SQLiteParameter("@title", strTitle));
					cmd.Parameters.Add(new SQLiteParameter("@artist", strArtist));
					cmd.Parameters.Add(new SQLiteParameter("@album", strAlbum));
					cmd.Prepare();

					nRet = cmd.ExecuteNonQuery();
					transaction.Commit();
				}
				return (nRet > 0) ? true : false;
			}
			catch (Exception)
			{
			}
			return false;
		}


		/// <summary>
		/// DBのデータ更新
		/// </summary>
		static bool SQL_UpdateTagInfo(int nID, DateTime dtLastWrite, string strTitle, string strArtist, string strAlbum)
		{
			if (nID < 0)
				return false;

			try
			{
				int nRet = -1;
				using (SQLiteTransaction transaction = _conn.BeginTransaction())
				using (SQLiteCommand cmd = _conn.CreateCommand())
				{
					cmd.CommandText = @"UPDATE TagInfo SET lastwritedate=@lastwritedate, title=@title, artist=@artist, album=@album WHERE id=@id;";
					cmd.Parameters.Add(new SQLiteParameter("@lastwritedate", dtLastWrite.Ticks));
					cmd.Parameters.Add(new SQLiteParameter("@title", strTitle));
					cmd.Parameters.Add(new SQLiteParameter("@artist", strArtist));
					cmd.Parameters.Add(new SQLiteParameter("@album", strAlbum));
					cmd.Parameters.Add(new SQLiteParameter("@id", nID));
					cmd.Prepare();

					nRet = cmd.ExecuteNonQuery();
					transaction.Commit();
				}
				return (nRet > 0) ? true : false;
			}
			catch (Exception)
			{
			}
			return false;
		}




		/// <summary>
		/// DB情報の構築
		/// </summary>
		public static int RefreshAllInfo(bool bAllClear = false)
		{
			int nCount = 0;

			// サブ・ディレクトも含める
			IEnumerable<string> listFiles = Directory.EnumerateFiles(Form1._option.strFolder, "*.*", SearchOption.AllDirectories);

			bool ret;
			string strArtist;
			string strTitle;
			string strAlbum;

			if (bAllClear)
			{
				//既存のDBデータを全削除
				try
				{
					using (SQLiteCommand cmd = _conn.CreateCommand())
					{
						cmd.CommandText = @"TRUNCATE TABLE TagInfo;";
						cmd.ExecuteNonQuery();
					}
				}
				catch (Exception)
				{
				}
			}


			foreach (string strFile in listFiles)
			{
				string strExt = Path.GetExtension(strFile).ToLower();
				if (strExt == "")
					continue;

				if (strExt == ".mp3" || strExt == ".m4a")
				{
					//タグ情報読み込み
					ret = Utility.GetMp3Info(strFile, out strTitle, out strArtist, out strAlbum);
					if (ret)
					{
						DateTime dtLastWrite = File.GetLastWriteTime(strFile);

						//DBへ追加
						if (bAllClear)
							ret = SQL_AddNewTagInfo(new AudioItem(strFile), dtLastWrite, strTitle, strArtist, strAlbum);
						else
							ret = SQL_SetTagInfo(new AudioItem(strFile), dtLastWrite, strTitle, strArtist, strAlbum);
						if (ret)
							nCount++;
					}
					continue;
				}

				//ZIPファイルなら再生できそうな全エントリーを追加
				if (strExt == ".zip")
				{
					try
					{
						DateTime dtLastWrite = File.GetLastWriteTime(strFile);
						using (ZipArchive archive = ZipFile.OpenRead(strFile))
						{
							for (int i = 0; i < archive.Entries.Count; i++)
							{
								strExt = Path.GetExtension(archive.Entries[i].FullName).ToLower();
								if (strExt == ".mp3" || strExt == ".m4a")
								{
									try
									{
										//解凍
										string strTmpFile = Utility.GetTmpFile() + strExt;
										archive.Entries[i].ExtractToFile(strTmpFile, true);

										//タグ情報読み込み
										ret = Utility.GetMp3Info(strTmpFile, out strTitle, out strArtist, out strAlbum);
										if (ret)
										{
											//DBへ追加
											if (bAllClear)
												ret = SQL_AddNewTagInfo(new AudioItem(strFile, i), dtLastWrite, strTitle, strArtist, strAlbum);
											else
												ret = SQL_SetTagInfo(new AudioItem(strFile, i), dtLastWrite, strTitle, strArtist, strAlbum);
											if (ret)
												nCount++;
										}

										//解凍したファイルの削除
										File.Delete(strTmpFile);
									}
									catch (Exception)
									{
									}
								}
							}
						}
					}
					catch (Exception)
					{
					}
				}

			}
			return nCount;
		}

	}
}
■Form1.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Windows.Forms;
using WMPLib;

namespace MP3Player
{
	public partial class Form1 : Form
	{
		WindowsMediaPlayer _mediaPlayer = new WindowsMediaPlayer();

		Timer _timer = new Timer();

		public static Option _option = new Option();


		List<AudioItem> _listFiles;			//音楽ファイル一覧
		int _nCurrentIndex = -1;			//↑へのインデックス


		List<int> _listPlayHistory = new List<int>();		//過去の再生曲
		List<int> _listPlayNext = new List<int>();			//未来の再生曲。なければランダム


		bool _bStopButtonPushed = false;	//「停止」ボタンを押したらtrue

		public Form1()
		{
			InitializeComponent();

			//設定の読み込み
			Option.Load(out _option, Utility.GetExeFolder() + "option.txt");

			Utility.CreateTmpFolder();			//tmpフォルダの作成

			//以下を追加
			//DBに情報がなければ再構築する=>時間が非常にかかる!
			if (MP3Info.GetCount() == 0)
			{
				MP3Info.RefreshAllInfo(true);
			}


			//mp3フォルダからの音楽ファイルリスト読み込み
			_listFiles = EnumFiles(_option.strFolder);


			//フォームが表示されたときの処理
			Shown += delegate
			{
				//シークバーにフォーカスがあると、再生位置が表示されないのでここで再生ボタンにフォーカスを与える
				buttonPlay.Focus();
			};


			//フォーム右クリックメニューの設定
			{
				ContextMenuStrip menu = new ContextMenuStrip();

				ToolStripItem[] stripItem = new ToolStripMenuItem[]
				{
					new ToolStripMenuItem("曲レーティング",null,new ToolStripMenuItem[]
					{
						new ToolStripMenuItem("なし", null, delegate
							{
								SetRating(0);
							}),
						new ToolStripMenuItem("★", null, delegate
							{
								SetRating(1);
							}),
						new ToolStripMenuItem("★★", null, delegate
							{
								SetRating(2);
							}),
						new ToolStripMenuItem("★★★", null, delegate
							{
								SetRating(3);
							}),
						new ToolStripMenuItem("★★★★", null, delegate
							{
								SetRating(4);
							}),
						new ToolStripMenuItem("★★★★★", null, delegate
							{
								SetRating(5);
							}),
					}),
					new ToolStripMenuItem("アーティストレーティング",null,new ToolStripMenuItem[]
					{
						new ToolStripMenuItem("なし", null, delegate
							{
								SetRatingArtist(0);
							}),
						new ToolStripMenuItem("★", null, delegate
							{
								SetRatingArtist(1);
							}),
						new ToolStripMenuItem("★★", null, delegate
							{
								SetRatingArtist(2);
							}),
						new ToolStripMenuItem("★★★", null, delegate
							{
								SetRatingArtist(3);
							}),
						new ToolStripMenuItem("★★★★", null, delegate
							{
								SetRatingArtist(4);
							}),
						new ToolStripMenuItem("★★★★★", null, delegate
							{
								SetRatingArtist(5);
							}),
					}),
					new ToolStripMenuItem("NG登録", null, delegate
						{
							if (_nCurrentIndex >= 0)
							{
								AddNGForm dlg = new AddNGForm(_listFiles[_nCurrentIndex], labelTitle.Text, labelArtist.Text);
								dlg.ShowDialog();
							}
						}),
					new ToolStripMenuItem("再生設定", null, delegate
						{
							PlayConditionForm dlg = new PlayConditionForm();
							dlg.ShowDialog(this);
						}),
					new ToolStripMenuItem("エクスプローラー", null, delegate
						{
							if (_nCurrentIndex >= 0)
							{
								Process.Start("explorer.exe","/select,\"" + _listFiles[_nCurrentIndex].strFile + "\"");
							}
						}),
					new ToolStripMenuItem("最小化", null, delegate
						{
							if (WindowState == FormWindowState.Normal)
								WindowState = FormWindowState.Minimized;
						}),
					new ToolStripMenuItem("終了", null, delegate
						{
							Close();
						}),
				};
				menu.Items.AddRange(stripItem);

				ContextMenuStrip = menu;
			}



			//終了時処理
			FormClosed += delegate
			{
				Utility.CleanTempFolder();			//tmpフォルダ内の削除

				//設定の保存
				_option.Save(Utility.GetExeFolder() + "option.txt");

				MP3Info.Close();		//追加
			};

プロジェクトファイルをダウンロード


カテゴリー「MP3プレーヤーを作る(C#)」 のエントリー