Browse Source

commit: import project

HOME 4 years ago
parent
commit
eb559d91f7

+ 76 - 0
Id3TagBatchProcess/Id3TagBatchProcess.csproj

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props" Condition="Exists('C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props')" />
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{165C4021-2755-49B9-AB25-3EDAB449E2D5}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>Id3TagBatchProcess</RootNamespace>
+    <AssemblyName>Id3TagBatchProcess</AssemblyName>
+    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="policy.2.0.taglib-sharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=db62eba44689b5b0, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\taglib.2.1.0.0\lib\policy.2.0.taglib-sharp.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+    <Reference Include="taglib-sharp, Version=2.1.0.0, Culture=neutral, PublicKeyToken=db62eba44689b5b0, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\taglib.2.1.0.0\lib\taglib-sharp.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props')" Text="$([System.String]::Format('$(ErrorText)', 'C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props'))" />
+  </Target>
+  <PropertyGroup>
+    <PostBuildEvent>if $(ConfigurationName) == Release if not exist $(TargetDir)Packed md $(TargetDir)Packed
+if $(ConfigurationName) == Release $(ILRepack) /ndebug /out:$(TargetDir)Packed\$(TargetFileName) $(TargetPath) $(TargetDir)taglib-sharp.dll</PostBuildEvent>
+  </PropertyGroup>
+</Project>

+ 190 - 0
Id3TagBatchProcess/Program.cs

@@ -0,0 +1,190 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net.Mime;
+using System.Reflection;
+using System.Text;
+using TagLib;
+using File = System.IO.File;
+// ReSharper disable InconsistentNaming
+
+namespace Id3TagBatchProcess
+{
+    internal class Program
+    {
+        private static void Main(string[] args)
+        {
+            Console.WriteLine("Id3 Tag Batch Procesor");
+            Console.WriteLine();
+
+            try
+            {
+                RealMain(args);
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine($"ERROR:{e.Message}");
+            }
+
+            Console.WriteLine();
+            Console.Write("Press ENTER to exit...");
+            Console.ReadLine();
+        }
+
+        private const string OP_DIR_EMB_COVER_MP3 = "embcover_mp3_jpg_dir";
+        private const string OP_DIR_EMB_COVER_M4A = "embcover_m4a_jpg_dir";
+        private const string OP_EMB_COVER = "embcover";
+        private const string OP_EMB_LRC = "emblrc";
+
+        private static void RealMain(string[] args)
+        {
+            if (0 == args.Length)
+            {
+                Console.WriteLine($"Useage: {Path.GetFileName(Assembly.GetExecutingAssembly().CodeBase)} <op> [args...]");
+                Console.WriteLine($"Available ops:");
+                Console.WriteLine($" *  {OP_EMB_LRC} <inputPath> <lrcPath> <outputPath>");
+                Console.WriteLine($"        Embed lyrics into file(utf8)");
+                Console.WriteLine($" *  {OP_EMB_COVER} <inputPath> <coverPath> <outputPath> [mime-type]");
+                Console.WriteLine($"        Embed cover into file, default mine-type is {MediaTypeNames.Image.Jpeg}");
+                Console.WriteLine($" *  {OP_DIR_EMB_COVER_MP3} <inputDir> <outputDir> -- lookup dir, embed same file name jpg/jpeg into mp3");
+                Console.WriteLine($"        Lookup dir, embed same file name jpg/jpeg into mp3");
+                Console.WriteLine($"        Embed abc.jpg or abc.jpeg into abc.mp3 and save a copy to output dir.");
+                Console.WriteLine($" *  {OP_DIR_EMB_COVER_M4A} <inputDir> <outputDir> -- lookup dir, embed same file name jpg/jpeg into m4a");
+                Console.WriteLine($"        Lookup dir, embed same file name jpg/jpeg into m4a");
+                Console.WriteLine($"        Embed abc.jpg or abc.jpeg into abc.m4a and save a copy to output dir.");
+
+                Console.WriteLine($"");
+                Console.WriteLine($" +  More on demand");
+                return;
+            }
+
+            var op = args[0];
+            var argsToOp = args.Skip(1).ToArray();
+
+            switch (op)
+            {
+                default: throw new ArgumentException($"No recognizable op:{op}");
+                case OP_DIR_EMB_COVER_MP3: EmbJpegCoverDir(argsToOp, "*.mp3"); break;
+                case OP_DIR_EMB_COVER_M4A: EmbJpegCoverDir(argsToOp, "*.m4a"); break;
+                case OP_EMB_COVER: EmbCover(argsToOp); break;
+                case OP_EMB_LRC: EmbLrc(argsToOp); break;
+            }
+        }
+
+        private static void EmbCover(string[] args)
+        {
+            if (args.Length < 3) throw new ArgumentException("args need 3");
+            var pathIn = args[0];
+            var pathCover = args[1];
+            var pathOutput = args[2];
+            var mimeType = args.Length > 3 ? args[4] : MediaTypeNames.Image.Jpeg;
+
+            if (false == File.Exists(pathIn)) throw new DirectoryNotFoundException($"inputPath `{pathIn}' not found.");
+            if (false == File.Exists(pathIn)) throw new DirectoryNotFoundException($"coverPath `{pathCover}' not found.");
+
+            Console.WriteLine($"Processing   {pathIn}...");
+            Console.WriteLine($"Comver image:{pathCover}");
+
+            File.Copy(pathIn, pathOutput);
+
+            EmbedConverIntoFile(pathOutput, pathCover, mimeType);
+
+            //                  12345678901234
+            Console.WriteLine($"Saved to:    {pathOutput}");
+        }
+
+        private static void EmbLrc(string[] args)
+        {
+            if (args.Length < 3) throw new ArgumentException("args need 2");
+            var pathIn = args[0];
+            var pathLrc = args[1];
+            var pathOutput = args[2];
+
+            if (false == File.Exists(pathIn)) throw new DirectoryNotFoundException($"inputPath `{pathIn}' not found.");
+            if (false == File.Exists(pathIn)) throw new DirectoryNotFoundException($"coverPath `{pathLrc}' not found.");
+
+            Console.WriteLine($"Processing   {pathIn}...");
+            Console.WriteLine($"Comver image:{pathLrc}");
+
+            File.Copy(pathIn, pathOutput);
+
+            EmbedLrcIntoFile(pathOutput, pathLrc);
+
+            //                  12345678901234
+            Console.WriteLine($"Saved to:    {pathOutput}");
+        }
+
+        // impl
+
+        private static void EmbJpegCoverDir(string[] args, string pattern)
+        {
+            if (args.Length != 2) throw new ArgumentException("args need 2");
+
+            var dirIn = args[0];
+            var dirOut = args[1];
+
+            if (false == Directory.Exists(dirIn)) throw new DirectoryNotFoundException($"inputDir `{dirIn}' not found.");
+            if (false == Directory.Exists(dirOut)) throw new DirectoryNotFoundException($"outputDir `{dirOut} not found.'");
+
+            Console.WriteLine($"Enter directory:{dirIn}");
+            var files = Directory.GetFiles(dirIn, pattern);
+            if (0 == files.Length)
+            {
+                Console.WriteLine($"Not result for {pattern}");
+            }
+            else
+            {
+                foreach (var file in files)
+                {
+                    //                  12345678901234
+                    Console.WriteLine($"Processing   {file}...");
+
+                    var imgFilePath = Path.ChangeExtension(file, ".jpg");
+                    if (false == File.Exists(imgFilePath)) imgFilePath = Path.ChangeExtension(file, ".jpeg");
+                    if (false == File.Exists(imgFilePath))
+                    {
+                        Console.WriteLine("Cover image file not found, Skipped. ***");
+                        continue;
+                    }
+
+                    //                  12345678901234
+                    Console.WriteLine($"Comver image:{imgFilePath}");
+
+                    var destFileName = Path.Combine(dirOut, Path.GetFileName(file));
+                    File.Copy(file, destFileName);
+
+                    EmbedConverIntoFile(destFileName, imgFilePath, MediaTypeNames.Image.Jpeg);
+
+                    //                  12345678901234
+                    Console.WriteLine($"Saved to:    {destFileName}");
+                }
+            }
+            Console.WriteLine($"Leave directory:{dirIn}");
+        }
+
+        private static void EmbedConverIntoFile(string filePath, string coverPath, string mimeType)
+        {
+            var mediaFile = TagLib.File.Create(filePath);
+
+            var pic = new Picture //REF: stackoverflow.com/a/30285220/2430943
+            {
+                Type = PictureType.FrontCover,
+                Description = "Cover",
+                MimeType = mimeType,
+                Data = File.ReadAllBytes(coverPath)
+            };
+            mediaFile.Tag.Pictures = new IPicture[] { pic };
+
+            mediaFile.Save();
+        }
+
+        private static void EmbedLrcIntoFile(string filePath, string lrcPath)
+        {
+            var mediaFile = TagLib.File.Create(filePath);
+
+            mediaFile.Tag.Lyrics = File.ReadAllText(lrcPath,Encoding.UTF8);
+
+            mediaFile.Save();
+        }
+    }
+}

+ 35 - 0
Id3TagBatchProcess/Properties/AssemblyInfo.cs

@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("Id3TagBatchProcess")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("Id3TagBatchProcess")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("165c4021-2755-49b9-ab25-3edab449e2d5")]
+
+// 程序集的版本信息由下列四个值组成:
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
+// 方法是按如下所示使用“*”: :
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 5 - 0
Id3TagBatchProcess/packages.config

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="ILRepack" version="2.0.18" targetFramework="net461" />
+  <package id="taglib" version="2.1.0.0" targetFramework="net461" />
+</packages>

+ 10 - 0
LyricTools/DebugLauncherProgram.cs

@@ -0,0 +1,10 @@
+using System;
+
+namespace LyricToolsNet4DebugLauncher
+{
+    internal class DebugLauncherProgram
+    {
+        [STAThread]
+        private static void Main() => LyricTools.Program.Main();
+    }
+}

+ 57 - 0
LyricTools/HiraganaPhoneticHelper.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace LyricTools
+{
+    public class HiraganaPhoneticHelper : IDisposable
+    {
+        private const string ProgId = "MSIME.Japan";
+
+        private readonly IFELanguage _instance;
+
+        public HiraganaPhoneticHelper()
+        {
+            if (!(Activator.CreateInstance(Type.GetTypeFromProgID(ProgId)) is IFELanguage ifelang))
+            {
+                throw new Exception($"Could not create object of {ProgId}, is STAThread attribute missing?");
+            }
+
+            var hr = ifelang.Open();
+            if (hr != 0) throw Marshal.GetExceptionForHR(hr);
+
+            hr = ifelang.GetPhonetic("東京は昨日雪が降りました。", 1, -1, out _);
+            if (hr != 0) throw Marshal.GetExceptionForHR(hr);
+
+            _instance = ifelang;
+        }
+
+        public string GetPhonetic(string input)
+        {
+            var hr = _instance.GetPhonetic(input, 1, -1, out var result);
+            if (hr != 0) throw Marshal.GetExceptionForHR(hr);
+            return result;
+        }
+
+        public void Dispose() => _instance?.Close();
+
+        // IFELanguage2 Interface ID
+        //[Guid("21164102-C24A-11d1-851A-00C04FCC6B14")]
+        [ComImport]
+        [Guid("019F7152-E6DB-11d0-83C3-00C04FDDB82E")]
+        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+        private interface IFELanguage
+        {
+            int Open();
+
+            int Close();
+
+            int GetJMorphResult(uint dwRequest, uint dwCMode, int cwchInput, [MarshalAs(UnmanagedType.LPWStr)] string pwchInput, IntPtr pfCInfo, out object ppResult);
+
+            int GetConversionModeCaps(ref uint pdwCaps);
+
+            int GetPhonetic([MarshalAs(UnmanagedType.BStr)] string @string, int start, int length, [MarshalAs(UnmanagedType.BStr)] out string result);
+
+            int GetConversion([MarshalAs(UnmanagedType.BStr)] string @string, int start, int length, [MarshalAs(UnmanagedType.BStr)] out string result);
+        }
+    }
+}

+ 219 - 0
LyricTools/LrcFetcherPage.Designer.cs

@@ -0,0 +1,219 @@
+namespace LyricTools
+{
+    partial class LrcFetcherPage
+    {
+        /// <summary> 
+        /// 必需的设计器变量。
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary> 
+        /// 清理所有正在使用的资源。
+        /// </summary>
+        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region 组件设计器生成的代码
+
+        /// <summary> 
+        /// 设计器支持所需的方法 - 不要修改
+        /// 使用代码编辑器修改此方法的内容。
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.label1 = new System.Windows.Forms.Label();
+            this.SongUrlTextBox = new System.Windows.Forms.TextBox();
+            this.PasteLinkLabel = new System.Windows.Forms.LinkLabel();
+            this.label2 = new System.Windows.Forms.Label();
+            this.SongIdTextBox = new System.Windows.Forms.TextBox();
+            this.label3 = new System.Windows.Forms.Label();
+            this.LrcApiTextBox = new System.Windows.Forms.TextBox();
+            this.panel1 = new System.Windows.Forms.Panel();
+            this.CopyButton = new System.Windows.Forms.Button();
+            this.FireButton = new System.Windows.Forms.Button();
+            this.splitContainer1 = new System.Windows.Forms.SplitContainer();
+            this.LeftRichTextBox = new System.Windows.Forms.RichTextBox();
+            this.RightRichTextBox = new System.Windows.Forms.RichTextBox();
+            this.panel1.SuspendLayout();
+            this.splitContainer1.Panel1.SuspendLayout();
+            this.splitContainer1.Panel2.SuspendLayout();
+            this.splitContainer1.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // label1
+            // 
+            this.label1.AutoSize = true;
+            this.label1.Location = new System.Drawing.Point(6, 6);
+            this.label1.Name = "label1";
+            this.label1.Size = new System.Drawing.Size(47, 12);
+            this.label1.TabIndex = 0;
+            this.label1.Text = "NCM Url";
+            // 
+            // SongUrlTextBox
+            // 
+            this.SongUrlTextBox.Location = new System.Drawing.Point(59, 3);
+            this.SongUrlTextBox.Name = "SongUrlTextBox";
+            this.SongUrlTextBox.Size = new System.Drawing.Size(357, 21);
+            this.SongUrlTextBox.TabIndex = 1;
+            this.SongUrlTextBox.Text = "http://music.163.com/song?id=730669";
+            // 
+            // PasteLinkLabel
+            // 
+            this.PasteLinkLabel.AutoSize = true;
+            this.PasteLinkLabel.Location = new System.Drawing.Point(422, 6);
+            this.PasteLinkLabel.Name = "PasteLinkLabel";
+            this.PasteLinkLabel.Size = new System.Drawing.Size(47, 12);
+            this.PasteLinkLabel.TabIndex = 2;
+            this.PasteLinkLabel.TabStop = true;
+            this.PasteLinkLabel.Text = "←Paste";
+            this.PasteLinkLabel.Click += new System.EventHandler(this.PasteLinkLabel_Click);
+            // 
+            // label2
+            // 
+            this.label2.AutoSize = true;
+            this.label2.Location = new System.Drawing.Point(473, 6);
+            this.label2.Name = "label2";
+            this.label2.Size = new System.Drawing.Size(71, 12);
+            this.label2.TabIndex = 0;
+            this.label2.Text = "NCM Song ID";
+            // 
+            // SongIdTextBox
+            // 
+            this.SongIdTextBox.Location = new System.Drawing.Point(550, 3);
+            this.SongIdTextBox.Name = "SongIdTextBox";
+            this.SongIdTextBox.Size = new System.Drawing.Size(82, 21);
+            this.SongIdTextBox.TabIndex = 1;
+            this.SongIdTextBox.Text = "730669";
+            // 
+            // label3
+            // 
+            this.label3.AutoSize = true;
+            this.label3.Location = new System.Drawing.Point(5, 33);
+            this.label3.Name = "label3";
+            this.label3.Size = new System.Drawing.Size(71, 12);
+            this.label3.TabIndex = 0;
+            this.label3.Text = "NCM LRC API";
+            // 
+            // LrcApiTextBox
+            // 
+            this.LrcApiTextBox.Location = new System.Drawing.Point(82, 30);
+            this.LrcApiTextBox.Name = "LrcApiTextBox";
+            this.LrcApiTextBox.Size = new System.Drawing.Size(306, 21);
+            this.LrcApiTextBox.TabIndex = 1;
+            this.LrcApiTextBox.Text = "http://music.163.com/api/song/media?id=730669";
+            // 
+            // panel1
+            // 
+            this.panel1.Controls.Add(this.CopyButton);
+            this.panel1.Controls.Add(this.FireButton);
+            this.panel1.Controls.Add(this.SongUrlTextBox);
+            this.panel1.Controls.Add(this.label1);
+            this.panel1.Controls.Add(this.PasteLinkLabel);
+            this.panel1.Controls.Add(this.label2);
+            this.panel1.Controls.Add(this.LrcApiTextBox);
+            this.panel1.Controls.Add(this.SongIdTextBox);
+            this.panel1.Controls.Add(this.label3);
+            this.panel1.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panel1.Location = new System.Drawing.Point(0, 0);
+            this.panel1.Name = "panel1";
+            this.panel1.Size = new System.Drawing.Size(982, 61);
+            this.panel1.TabIndex = 4;
+            // 
+            // CopyButton
+            // 
+            this.CopyButton.Location = new System.Drawing.Point(475, 29);
+            this.CopyButton.Name = "CopyButton";
+            this.CopyButton.Size = new System.Drawing.Size(75, 21);
+            this.CopyButton.TabIndex = 3;
+            this.CopyButton.Text = "Copy↘";
+            this.CopyButton.UseVisualStyleBackColor = true;
+            this.CopyButton.Click += new System.EventHandler(this.CopyButton_Click);
+            // 
+            // FireButton
+            // 
+            this.FireButton.Location = new System.Drawing.Point(394, 29);
+            this.FireButton.Name = "FireButton";
+            this.FireButton.Size = new System.Drawing.Size(75, 21);
+            this.FireButton.TabIndex = 3;
+            this.FireButton.Text = "Fire!";
+            this.FireButton.UseVisualStyleBackColor = true;
+            this.FireButton.Click += new System.EventHandler(this.FireButton_Click);
+            // 
+            // splitContainer1
+            // 
+            this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.splitContainer1.Location = new System.Drawing.Point(0, 61);
+            this.splitContainer1.Name = "splitContainer1";
+            // 
+            // splitContainer1.Panel1
+            // 
+            this.splitContainer1.Panel1.Controls.Add(this.LeftRichTextBox);
+            // 
+            // splitContainer1.Panel2
+            // 
+            this.splitContainer1.Panel2.Controls.Add(this.RightRichTextBox);
+            this.splitContainer1.Size = new System.Drawing.Size(982, 600);
+            this.splitContainer1.SplitterDistance = 465;
+            this.splitContainer1.TabIndex = 5;
+            // 
+            // LeftRichTextBox
+            // 
+            this.LeftRichTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.LeftRichTextBox.Location = new System.Drawing.Point(0, 0);
+            this.LeftRichTextBox.Name = "LeftRichTextBox";
+            this.LeftRichTextBox.ReadOnly = true;
+            this.LeftRichTextBox.Size = new System.Drawing.Size(465, 600);
+            this.LeftRichTextBox.TabIndex = 4;
+            this.LeftRichTextBox.Text = "";
+            // 
+            // RightRichTextBox
+            // 
+            this.RightRichTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.RightRichTextBox.Location = new System.Drawing.Point(0, 0);
+            this.RightRichTextBox.Name = "RightRichTextBox";
+            this.RightRichTextBox.ReadOnly = true;
+            this.RightRichTextBox.Size = new System.Drawing.Size(513, 600);
+            this.RightRichTextBox.TabIndex = 4;
+            this.RightRichTextBox.Text = "";
+            // 
+            // LrcFetcherPage
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.Controls.Add(this.splitContainer1);
+            this.Controls.Add(this.panel1);
+            this.Name = "LrcFetcherPage";
+            this.Size = new System.Drawing.Size(982, 661);
+            this.panel1.ResumeLayout(false);
+            this.panel1.PerformLayout();
+            this.splitContainer1.Panel1.ResumeLayout(false);
+            this.splitContainer1.Panel2.ResumeLayout(false);
+            this.splitContainer1.ResumeLayout(false);
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.Label label1;
+        private System.Windows.Forms.TextBox SongUrlTextBox;
+        private System.Windows.Forms.LinkLabel PasteLinkLabel;
+        private System.Windows.Forms.Label label2;
+        private System.Windows.Forms.TextBox SongIdTextBox;
+        private System.Windows.Forms.Label label3;
+        private System.Windows.Forms.TextBox LrcApiTextBox;
+        private System.Windows.Forms.Panel panel1;
+        private System.Windows.Forms.Button FireButton;
+        private System.Windows.Forms.SplitContainer splitContainer1;
+        private System.Windows.Forms.RichTextBox LeftRichTextBox;
+        private System.Windows.Forms.RichTextBox RightRichTextBox;
+        private System.Windows.Forms.Button CopyButton;
+    }
+}

+ 77 - 0
LyricTools/LrcFetcherPage.cs

@@ -0,0 +1,77 @@
+using System;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Web;
+using System.Windows.Forms;
+
+namespace LyricTools
+{
+    public partial class LrcFetcherPage : UserControl
+    {
+        public LrcFetcherPage()
+        {
+            InitializeComponent();
+        }
+
+        private void PasteLinkLabel_Click(object sender, EventArgs e)
+        {
+            var text = Clipboard.GetText();
+            var uri = new Uri(text);
+            var queryString = HttpUtility.ParseQueryString(uri.Query);
+            var songId = queryString["id"];
+
+            SongUrlTextBox.Text = text;
+            SongIdTextBox.Text = songId;
+            LrcApiTextBox.Text = $"http://music.163.com/api/song/media?id={songId}";
+        }
+
+        private delegate void Action();
+
+        private void FireButton_Click(object sender, EventArgs e)
+        {
+            var url = LrcApiTextBox.Text;
+
+            FireButton.Enabled = false;
+
+            new Thread(() =>
+            {
+                void Log(string text)
+                {
+                    Invoke(new Action(() =>
+                    {
+                        LeftRichTextBox.AppendText($"{text}{Environment.NewLine}");
+                        LeftRichTextBox.SelectionStart = LeftRichTextBox.Text.Length;
+                        LeftRichTextBox.ScrollToCaret();
+                    }));
+                }
+
+                try
+                {
+                    Log($"Fetching {url}");
+                    var json = new WebClient { Encoding = Encoding.UTF8 }.DownloadString(url);
+                    Log($"Result: {json}");
+                    var jsonObject = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(json);
+                    Log("Parsing...");
+                    var lrc = jsonObject.Value<string>("lyric");
+                    Log("Output...");
+                    Invoke(new Action(() => RightRichTextBox.Text = lrc));
+                    Log("OK");
+                }
+                catch (Exception exception)
+                {
+                    Log("---- BEGIN EXCEPTION ----");
+                    Log(exception.ToString());
+                    Log("---- END EXCEPTION ----");
+                }
+
+                Invoke(new Action(() => FireButton.Enabled = true));
+            }).Start();
+        }
+
+        private void CopyButton_Click(object sender, EventArgs e)
+        {
+            Clipboard.SetText(RightRichTextBox.Text);
+        }
+    }
+}

+ 120 - 0
LyricTools/LrcFetcherPage.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 131 - 0
LyricTools/LrcKanjiHiraganaInjectPage.cs

@@ -0,0 +1,131 @@
+using System;
+using System.Drawing;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Windows.Forms;
+
+namespace LyricTools
+{
+    internal static class LrcKanjiHiraganaInjectPage
+    {
+        public static Control Create(Form frmMain)
+        {
+            var split = new SplitContainer
+            {
+                Dock = DockStyle.Fill,
+                SplitterDistance = 70,
+                Font = new Font("MS Gothic", 9),
+                Orientation = Orientation.Vertical,
+            };
+
+
+            var rbLeft = new RichTextBox { Dock = DockStyle.Fill, AllowDrop = true };
+            var rbRight = new RichTextBox { Dock = DockStyle.Fill, ReadOnly = true };
+
+            split.Panel1.Controls.Add(rbLeft);
+            split.Panel2.Controls.Add(rbRight);
+
+            rbLeft.DragEnter += (sender, args) =>
+            {
+                string[] items;
+                args.Effect = args.Data.GetDataPresent(DataFormats.FileDrop)
+                              && 1 == (items = (string[])args.Data.GetData(DataFormats.FileDrop)).Length
+                              && File.Exists(items[0])
+                    ? DragDropEffects.Copy
+                    : DragDropEffects.None;
+            };
+
+            rbLeft.DragDrop += (sender, args) =>
+            {
+                var items = (string[])args.Data.GetData(DataFormats.FileDrop);
+                var bytes = File.ReadAllBytes(items[0]);
+
+                rbLeft.Text = 0 == args.KeyState
+                    ? Encoding.Default.GetString(bytes)
+                    : Encoding.UTF8.GetString(bytes);
+            };
+
+            rbLeft.TextChanged += (sender, args) =>
+            {
+                try
+                {
+                    rbRight.Text = HiraganaInject(rbLeft.Lines);
+                }
+                catch (Exception e)
+                {
+                    rbRight.Text = e.ToString();
+                }
+            };
+            frmMain.Shown += delegate
+            {
+                rbLeft.Text = "[00:00.00]漢字のひらがなを自動に生成する";
+            };
+
+            return split;
+        }
+
+        private static string HiraganaInject(string[] lines)
+        {
+            using (var helper = new HiraganaPhoneticHelper())
+            {
+                var sb = new StringBuilder();
+
+                foreach (var line in lines)
+                {
+                    //split by pattern
+                    // kanji-kana -> handle
+                    // other -> copy
+
+                    var preMatch = Regex.Match(line, @"(\[\d\d:\d\d\.\d\d])(.*)");
+                    var lineProcessed = preMatch.Groups[1].Value;
+                    var content = preMatch.Groups[2].Value;
+
+                    var matches = Regex.Matches(content, "(?:\\p{IsCJKUnifiedIdeographs}+?\\p{IsHiragana}+|\\p{IsCJKUnifiedIdeographs}+)");
+
+                    if (0 == matches.Count)
+                    {
+                        sb.AppendLine(line);
+                        continue;
+                    }
+
+                    var caret = 0;
+                    foreach (Match match in matches)
+                    {
+                        if (match.Index != caret) lineProcessed += content.Substring(caret, match.Index - caret);
+
+                        var matchValue = match.Value;
+                        if (false == Regex.IsMatch(matchValue, "\\p{IsCJKUnifiedIdeographs}+"))
+                        {
+                            lineProcessed += matchValue;
+                            caret = match.Index + match.Length;
+                            continue;
+                        }
+
+                        var result = helper.GetPhonetic(matchValue);
+
+                        var postMatch = Regex.Match(matchValue, "(\\w+?)(\\p{IsHiragana}+)");
+                        if (postMatch.Success)
+                        {
+                            var kanji = postMatch.Groups[1].Value;
+                            var after = postMatch.Groups[2].Value;
+                            lineProcessed += kanji + $"({result.Substring(0, result.Length - after.Length)})" + after;
+                        }
+                        else
+                        {
+                            lineProcessed += $"{matchValue}({result})";
+                        }
+
+                        caret = match.Index + match.Length;
+                    }
+
+                    if (caret != content.Length) lineProcessed += content.Substring(caret);
+
+                    sb.AppendLine(lineProcessed);
+                }
+                return sb.ToString();
+            }
+        }
+
+    }
+}

+ 195 - 0
LyricTools/LrcPreProcessPage.Designer.cs

@@ -0,0 +1,195 @@
+namespace LyricTools
+{
+    partial class LrcPreProcessPage
+    {
+        /// <summary> 
+        /// 必需的设计器变量。
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary> 
+        /// 清理所有正在使用的资源。
+        /// </summary>
+        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region 组件设计器生成的代码
+
+        /// <summary> 
+        /// 设计器支持所需的方法 - 不要修改
+        /// 使用代码编辑器修改此方法的内容。
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.RightRichTextBox = new System.Windows.Forms.RichTextBox();
+            this.panel1 = new System.Windows.Forms.Panel();
+            this.FullWidthSpaceToHalfWideCheckBox = new System.Windows.Forms.CheckBox();
+            this.RemoveBracketsCheckBox = new System.Windows.Forms.CheckBox();
+            this.Fixed2CheckBox = new System.Windows.Forms.CheckBox();
+            this.RemoveBlankLinesCheckBox = new System.Windows.Forms.CheckBox();
+            this.PasteButton = new System.Windows.Forms.Button();
+            this.FireButton = new System.Windows.Forms.Button();
+            this.splitContainer1 = new System.Windows.Forms.SplitContainer();
+            this.LeftRichTextBox = new System.Windows.Forms.RichTextBox();
+            this.panel1.SuspendLayout();
+            this.splitContainer1.Panel1.SuspendLayout();
+            this.splitContainer1.Panel2.SuspendLayout();
+            this.splitContainer1.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // RightRichTextBox
+            // 
+            this.RightRichTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.RightRichTextBox.Location = new System.Drawing.Point(0, 0);
+            this.RightRichTextBox.Name = "RightRichTextBox";
+            this.RightRichTextBox.ReadOnly = true;
+            this.RightRichTextBox.Size = new System.Drawing.Size(549, 715);
+            this.RightRichTextBox.TabIndex = 4;
+            this.RightRichTextBox.Text = "";
+            // 
+            // panel1
+            // 
+            this.panel1.Controls.Add(this.FullWidthSpaceToHalfWideCheckBox);
+            this.panel1.Controls.Add(this.RemoveBracketsCheckBox);
+            this.panel1.Controls.Add(this.Fixed2CheckBox);
+            this.panel1.Controls.Add(this.RemoveBlankLinesCheckBox);
+            this.panel1.Controls.Add(this.PasteButton);
+            this.panel1.Controls.Add(this.FireButton);
+            this.panel1.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panel1.Location = new System.Drawing.Point(0, 0);
+            this.panel1.Name = "panel1";
+            this.panel1.Size = new System.Drawing.Size(1050, 33);
+            this.panel1.TabIndex = 6;
+            // 
+            // FullWidthSpaceToHalfWideCheckBox
+            // 
+            this.FullWidthSpaceToHalfWideCheckBox.AutoSize = true;
+            this.FullWidthSpaceToHalfWideCheckBox.Checked = true;
+            this.FullWidthSpaceToHalfWideCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
+            this.FullWidthSpaceToHalfWideCheckBox.Location = new System.Drawing.Point(531, 6);
+            this.FullWidthSpaceToHalfWideCheckBox.Name = "FullWidthSpaceToHalfWideCheckBox";
+            this.FullWidthSpaceToHalfWideCheckBox.Size = new System.Drawing.Size(198, 16);
+            this.FullWidthSpaceToHalfWideCheckBox.TabIndex = 4;
+            this.FullWidthSpaceToHalfWideCheckBox.Text = "Full-width space to half-wide";
+            this.FullWidthSpaceToHalfWideCheckBox.UseVisualStyleBackColor = true;
+            // 
+            // RemoveBracketsCheckBox
+            // 
+            this.RemoveBracketsCheckBox.AutoSize = true;
+            this.RemoveBracketsCheckBox.Checked = true;
+            this.RemoveBracketsCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
+            this.RemoveBracketsCheckBox.Location = new System.Drawing.Point(399, 6);
+            this.RemoveBracketsCheckBox.Name = "RemoveBracketsCheckBox";
+            this.RemoveBracketsCheckBox.Size = new System.Drawing.Size(126, 16);
+            this.RemoveBracketsCheckBox.TabIndex = 4;
+            this.RemoveBracketsCheckBox.Text = "Remove \\(Hira+?\\)";
+            this.RemoveBracketsCheckBox.UseVisualStyleBackColor = true;
+            // 
+            // Fixed2CheckBox
+            // 
+            this.Fixed2CheckBox.AutoSize = true;
+            this.Fixed2CheckBox.Checked = true;
+            this.Fixed2CheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
+            this.Fixed2CheckBox.Location = new System.Drawing.Point(165, 6);
+            this.Fixed2CheckBox.Name = "Fixed2CheckBox";
+            this.Fixed2CheckBox.Size = new System.Drawing.Size(90, 16);
+            this.Fixed2CheckBox.TabIndex = 4;
+            this.Fixed2CheckBox.Text = "Fixed 0.00]";
+            this.Fixed2CheckBox.UseVisualStyleBackColor = true;
+            // 
+            // RemoveBlankLinesCheckBox
+            // 
+            this.RemoveBlankLinesCheckBox.AutoSize = true;
+            this.RemoveBlankLinesCheckBox.Checked = true;
+            this.RemoveBlankLinesCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
+            this.RemoveBlankLinesCheckBox.Location = new System.Drawing.Point(261, 6);
+            this.RemoveBlankLinesCheckBox.Name = "RemoveBlankLinesCheckBox";
+            this.RemoveBlankLinesCheckBox.Size = new System.Drawing.Size(132, 16);
+            this.RemoveBlankLinesCheckBox.TabIndex = 4;
+            this.RemoveBlankLinesCheckBox.Text = "Remove blank lines";
+            this.RemoveBlankLinesCheckBox.UseVisualStyleBackColor = true;
+            // 
+            // PasteButton
+            // 
+            this.PasteButton.Location = new System.Drawing.Point(3, 3);
+            this.PasteButton.Name = "PasteButton";
+            this.PasteButton.Size = new System.Drawing.Size(75, 21);
+            this.PasteButton.TabIndex = 3;
+            this.PasteButton.Text = "Paste↓";
+            this.PasteButton.UseVisualStyleBackColor = true;
+            this.PasteButton.Click += new System.EventHandler(this.PasteButton_Click);
+            // 
+            // FireButton
+            // 
+            this.FireButton.Location = new System.Drawing.Point(84, 3);
+            this.FireButton.Name = "FireButton";
+            this.FireButton.Size = new System.Drawing.Size(75, 21);
+            this.FireButton.TabIndex = 3;
+            this.FireButton.Text = "Fire!";
+            this.FireButton.UseVisualStyleBackColor = true;
+            this.FireButton.Click += new System.EventHandler(this.FireButton_Click);
+            // 
+            // splitContainer1
+            // 
+            this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.splitContainer1.Location = new System.Drawing.Point(0, 33);
+            this.splitContainer1.Name = "splitContainer1";
+            // 
+            // splitContainer1.Panel1
+            // 
+            this.splitContainer1.Panel1.Controls.Add(this.LeftRichTextBox);
+            // 
+            // splitContainer1.Panel2
+            // 
+            this.splitContainer1.Panel2.Controls.Add(this.RightRichTextBox);
+            this.splitContainer1.Size = new System.Drawing.Size(1050, 715);
+            this.splitContainer1.SplitterDistance = 497;
+            this.splitContainer1.TabIndex = 7;
+            // 
+            // LeftRichTextBox
+            // 
+            this.LeftRichTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.LeftRichTextBox.Location = new System.Drawing.Point(0, 0);
+            this.LeftRichTextBox.Name = "LeftRichTextBox";
+            this.LeftRichTextBox.Size = new System.Drawing.Size(497, 715);
+            this.LeftRichTextBox.TabIndex = 4;
+            this.LeftRichTextBox.Text = "";
+            // 
+            // LrcPreProcessPage
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.Controls.Add(this.splitContainer1);
+            this.Controls.Add(this.panel1);
+            this.Name = "LrcPreProcessPage";
+            this.Size = new System.Drawing.Size(1050, 748);
+            this.panel1.ResumeLayout(false);
+            this.panel1.PerformLayout();
+            this.splitContainer1.Panel1.ResumeLayout(false);
+            this.splitContainer1.Panel2.ResumeLayout(false);
+            this.splitContainer1.ResumeLayout(false);
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.RichTextBox RightRichTextBox;
+        private System.Windows.Forms.Panel panel1;
+        private System.Windows.Forms.CheckBox RemoveBracketsCheckBox;
+        private System.Windows.Forms.CheckBox Fixed2CheckBox;
+        private System.Windows.Forms.CheckBox RemoveBlankLinesCheckBox;
+        private System.Windows.Forms.Button PasteButton;
+        private System.Windows.Forms.Button FireButton;
+        private System.Windows.Forms.SplitContainer splitContainer1;
+        private System.Windows.Forms.RichTextBox LeftRichTextBox;
+        private System.Windows.Forms.CheckBox FullWidthSpaceToHalfWideCheckBox;
+    }
+}

+ 73 - 0
LyricTools/LrcPreProcessPage.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Windows.Forms;
+
+namespace LyricTools
+{
+    public partial class LrcPreProcessPage : UserControl
+    {
+        public LrcPreProcessPage()
+        {
+            InitializeComponent();
+        }
+
+        private void PasteButton_Click(object sender, EventArgs e)
+        {
+            LeftRichTextBox.Text = Clipboard.GetText();
+        }
+
+        private delegate void Action();
+
+        private void FireButton_Click(object sender, EventArgs e)
+        {
+            var lrc = LeftRichTextBox.Text;
+            var shouldFixFloatPoint = Fixed2CheckBox.Checked;
+            var shouldRemoveBlankLines = RemoveBlankLinesCheckBox.Checked;
+            var shouldRemoveBrackets = RemoveBracketsCheckBox.Checked;
+            var shouldChangeFullWidthSpaceToHalfWide = FullWidthSpaceToHalfWideCheckBox.Checked;
+
+            FireButton.Enabled = false;
+            new Thread(() =>
+            {
+                try
+                {
+                    if (shouldFixFloatPoint)
+                    {
+                        lrc = Regex.Replace(lrc, @"(\[\d\d\:\d\d.\d\d)\d(\])", @"$1$2");
+                        lrc = Regex.Replace(lrc, @"(\[\d\d\:\d\d.\d)(\])", @"${1}0$2");
+                    }
+
+                    if (shouldRemoveBlankLines)
+                    {
+                        lrc = string.Join(Environment.NewLine,
+                            Regex.Replace(lrc, @"^\[\d\d\:\d\d.\d\d\]\s*$", "", RegexOptions.Multiline)
+                                .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
+                        );
+                    }
+
+                    if (shouldRemoveBrackets)
+                    {
+                        lrc = string.Join(Environment.NewLine,
+                            Regex.Replace(lrc, @"[(\(]\p{IsHiragana}+?[\))]", "")
+                                .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
+                        );
+                    }
+
+                    if (shouldChangeFullWidthSpaceToHalfWide)
+                    {
+                        lrc = lrc.Replace(" ", " ");
+                    }
+
+                    Invoke(new Action(() => RightRichTextBox.Text = lrc));
+                }
+                catch (Exception exception)
+                {
+                    MessageBox.Show(exception.ToString());
+                }
+
+                Invoke(new Action(() => FireButton.Enabled = true));
+            }).Start();
+        }
+    }
+}

+ 120 - 0
LyricTools/LrcPreProcessPage.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 100 - 0
LyricTools/LrcTool.csproj

@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props" Condition="Exists('C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props')" />
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{D9749FB1-3DC8-4967-A74D-9D4139950BB2}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>LrcTool</RootNamespace>
+    <AssemblyName>LrcTool</AssemblyName>
+    <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+    <TargetFrameworkProfile />
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\Newtonsoft.Json.12.0.2\lib\net20\Newtonsoft.Json.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="LrcFetcherPage.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="LrcFetcherPage.Designer.cs">
+      <DependentUpon>LrcFetcherPage.cs</DependentUpon>
+    </Compile>
+    <Compile Include="LrcKanjiHiraganaInjectPage.cs" />
+    <Compile Include="HiraganaPhoneticHelper.cs" />
+    <Compile Include="LrcPreProcessPage.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="LrcPreProcessPage.Designer.cs">
+      <DependentUpon>LrcPreProcessPage.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="LrcFetcherPage.resx">
+      <DependentUpon>LrcFetcherPage.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="LrcPreProcessPage.resx">
+      <DependentUpon>LrcPreProcessPage.cs</DependentUpon>
+    </EmbeddedResource>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props')" Text="$([System.String]::Format('$(ErrorText)', 'C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props'))" />
+  </Target>
+  <PropertyGroup>
+    <PostBuildEvent>if $(ConfigurationName) == Release setlocal enabledelayedexpansion enableextensions
+    if $(ConfigurationName) == Release set DLL_LIST=
+    if $(ConfigurationName) == Release for %25%25x in ($(TargetDir)*.dll) do set DLL_LIST=!DLL_LIST! "%25%25x"
+    if $(ConfigurationName) == Release echo dlls: !DLL_LIST!
+
+if $(ConfigurationName) == Release if not exist "$(TargetDir)Packed" md "$(TargetDir)Packed"
+
+if $(ConfigurationName) == Release $(ILRepack) /ndebug "/out:$(TargetDir)Packed\$(TargetFileName)" "$(TargetPath)" !DLL_LIST!
+
+if $(ConfigurationName) == Release if exist "$(TargetDir)Packed\$(TargetFileName).config" del "$(TargetDir)Packed\$(TargetFileName).config"</PostBuildEvent>
+  </PropertyGroup>
+</Project>

+ 70 - 0
LyricTools/LrcToolNet4DebugLauncher.csproj

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props" Condition="Exists('C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props')" />
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{4729633E-E50E-47F4-AC9B-38412BB5D693}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>LrcToolNet4DebugLauncher</RootNamespace>
+    <AssemblyName>LrcToolNet4DebugLauncher</AssemblyName>
+    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+    <TargetFrameworkProfile />
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Prefer32Bit>false</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Prefer32Bit>false</Prefer32Bit>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="DebugLauncherProgram.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="LrcTool.csproj">
+      <Project>{d9749fb1-3dc8-4967-a74d-9d4139950bb2}</Project>
+      <Name>LrcTool</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props')" Text="$([System.String]::Format('$(ErrorText)', 'C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props'))" />
+  </Target>
+  <PropertyGroup>
+    <PostBuildEvent>
+    </PostBuildEvent>
+  </PropertyGroup>
+</Project>

+ 41 - 0
LyricTools/Program.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Windows.Forms;
+
+namespace LyricTools
+{
+    public static class Program
+    {
+        [STAThread]
+        public static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+
+            var frmMain = new Form { Text = Application.ProductName, Width = 1024, Height = 768 };
+
+            var tabs = new TabControl
+            {
+                Dock = DockStyle.Fill,
+                TabPages =
+                {
+                    new TabPage("LRC Kanji Hiragana inject")
+                    {
+                        Controls = {LrcKanjiHiraganaInjectPage.Create(frmMain)}
+                    },
+                    new TabPage("LRC Fetcher")
+                    {
+                        Controls = {new LrcFetcherPage{Dock = DockStyle.Fill}}
+                    },
+                    new TabPage("LRC PreProcessing")
+                    {
+                        Controls = { new LrcPreProcessPage { Dock = DockStyle.Fill } }
+                    }
+                }
+            };
+
+            frmMain.Controls.Add(tabs);
+
+            Application.Run(frmMain);
+        }
+    }
+}

+ 35 - 0
LyricTools/Properties/AssemblyInfo.cs

@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("LyricTools")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("LyricTools")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("d9749fb1-3dc8-4967-a74d-9d4139950bb2")]
+
+// 程序集的版本信息由下列四个值组成:
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
+// 方法是按如下所示使用“*”: :
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 5 - 0
LyricTools/packages.config

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="ILRepack" version="2.0.18" targetFramework="net20" />
+  <package id="Newtonsoft.Json" version="12.0.2" targetFramework="net20" />
+</packages>

+ 41 - 0
MusicTool.sln

@@ -0,0 +1,41 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29728.190
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Id3TagBatchProcess", "Id3TagBatchProcess\Id3TagBatchProcess.csproj", "{165C4021-2755-49B9-AB25-3EDAB449E2D5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LrcTool", "LyricTools\LrcTool.csproj", "{D9749FB1-3DC8-4967-A74D-9D4139950BB2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LrcToolNet4DebugLauncher", "LyricTools\LrcToolNet4DebugLauncher.csproj", "{4729633E-E50E-47F4-AC9B-38412BB5D693}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{165C4021-2755-49B9-AB25-3EDAB449E2D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{165C4021-2755-49B9-AB25-3EDAB449E2D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{165C4021-2755-49B9-AB25-3EDAB449E2D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{165C4021-2755-49B9-AB25-3EDAB449E2D5}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D9749FB1-3DC8-4967-A74D-9D4139950BB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D9749FB1-3DC8-4967-A74D-9D4139950BB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D9749FB1-3DC8-4967-A74D-9D4139950BB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D9749FB1-3DC8-4967-A74D-9D4139950BB2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{4729633E-E50E-47F4-AC9B-38412BB5D693}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{4729633E-E50E-47F4-AC9B-38412BB5D693}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4729633E-E50E-47F4-AC9B-38412BB5D693}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4729633E-E50E-47F4-AC9B-38412BB5D693}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {BDEE92B2-5D80-4E4A-AC0A-6076FF9F84B4}
+	EndGlobalSection
+	GlobalSection(SubversionScc) = preSolution
+		Svn-Managed = True
+		Manager = AnkhSVN - Subversion Support for Visual Studio
+	EndGlobalSection
+EndGlobal

+ 6 - 0
NuGet.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?> 
+<configuration> 
+  <config> 
+    <add key="repositorypath" value="C:\NuGetLocalRepo" /> 
+  </config> 
+</configuration>