Browse Source

CueSplitterGUI: Abort(?)
CueSplitterCli:Added

HOME 1 year ago
parent
commit
9545ddde93

+ 12 - 0
CueCli/CueCli.csproj

@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net5.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="taglib-sharp-netstandard2.0" Version="2.1.0" />
+  </ItemGroup>
+
+</Project>

+ 43 - 0
CueCli/Helpers/CueHelper.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using CueCli.Models;
+
+namespace CueCli.Helpers
+{
+    internal class CueHelper
+    {
+        public static TrackModel[] ParseCue(string cueText, TimeSpan totalDuration)
+        {
+            var cue = new CueSharp_N.CueSheet(cueText, (char[])null);
+
+            var convertedTracks = new List<TrackModel>(cue.Tracks.Length);
+
+            TrackModel lastTrk = null;
+            foreach (var track in cue.Tracks)
+            {
+                if (lastTrk != null)
+                {
+                    lastTrk.Duration = track.Offset - lastTrk.Offset;
+                }
+
+                var model = new TrackModel
+                {
+                    TrackNo = track.TrackNumber,
+                    Offset = track.Offset,
+                    Performer = track.Performer,
+                    Title = track.Title,
+                };
+
+                lastTrk = model;
+                convertedTracks.Add(model);
+            }
+
+            if (lastTrk != null)
+            {
+                lastTrk.Duration = totalDuration - lastTrk.Offset;
+            }
+
+            return convertedTracks.ToArray();
+        }
+    }
+}

+ 41 - 0
CueCli/Helpers/FfMpegHelper.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Diagnostics;
+// ReSharper disable StringLiteralTypo
+
+namespace CueCli.Helpers
+{
+    internal static class FfMpegHelper
+    {
+        public static string[] CutSegment(string inputFilePath, string outputFilePath, TimeSpan offset, TimeSpan duration)
+        {
+            return new string[]
+            {
+                "-n","-hide_banner","-stats",
+                "-v","warning",
+                "-i", inputFilePath,
+                "-c", "copy",
+                "-ss",offset.ToFfMpegTimeStamp(),
+                "-t",duration.ToFfMpegTimeStamp(),
+                outputFilePath
+            };
+        }
+
+        private static string ToFfMpegTimeStamp(this TimeSpan t) => $"{t.Hours:00}:{t.Minutes:00}:{t.Seconds:00}.{t.Milliseconds:000}";
+
+        public static Process Launch(string[] args)
+        {
+            var process = new Process
+            {
+                StartInfo =
+                {
+                    FileName = "ffmpeg",
+                    CreateNoWindow = false,
+                    UseShellExecute = false,
+                }
+            };
+            foreach (var item in args) process.StartInfo.ArgumentList.Add(item);
+            process.Start();
+            return process;
+        }
+    }
+}

+ 41 - 0
CueCli/Helpers/FilenameHelper.cs

@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace CueCli.Helpers
+{
+    internal static class FilenameHelper
+    {
+        private static char[] _invalidCharacters=null;
+
+        private static char[] GetInvalidCharacters()
+        {
+            var invalidChars = new List<char>();
+            invalidChars.AddRange(Path.GetInvalidFileNameChars());
+            invalidChars.AddRange(Path.GetInvalidPathChars());
+            return invalidChars.ToArray();
+        }
+
+        public static char[] InvalidCharacters => _invalidCharacters ??= GetInvalidCharacters();
+
+        public static string RemoveInvalidCharacters(string content, char replace = '_', bool doNotReplaceBackslashes = false)
+        {
+            if (string.IsNullOrEmpty(content))
+                return content;
+
+            var idx = content.IndexOfAny(InvalidCharacters);
+            if (idx >= 0)
+            {
+                var sb = new StringBuilder(content);
+                while (idx >= 0)
+                {
+                    if (sb[idx] != '\\' || !doNotReplaceBackslashes)
+                        sb[idx] = replace;
+                    idx = content.IndexOfAny(InvalidCharacters, idx + 1);
+                }
+                return sb.ToString();
+            }
+            return content;
+        }
+    }
+}

+ 51 - 0
CueCli/Helpers/TextHelper.cs

@@ -0,0 +1,51 @@
+using System;
+using System.Text;
+
+namespace CueCli.Helpers
+{
+    internal static class TextHelper
+    {
+        static TextHelper()
+        {
+            try
+            {
+                Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+            }
+            catch
+            {
+                //EAT ERR
+            }
+        }
+
+        private static Encoding TestCodePage(Encoding testCode, byte[] byteArray)
+        {
+            try
+            {
+                var encoding = Encoding.GetEncoding(testCode.CodePage, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
+                var a = encoding.GetCharCount(byteArray);
+                return testCode;
+            }
+            catch (Exception e)
+            {
+                return null;
+            }
+        }
+
+        public static Encoding DetectCodePage(byte[] contents)
+        {
+            if (contents == null || contents.Length == 0)
+            {
+                return Encoding.Default;
+            }
+
+            return TestCodePage(Encoding.UTF8, contents)
+                   ?? TestCodePage(Encoding.GetEncoding("SHIFT-JIS"), contents)
+                   ?? TestCodePage(Encoding.GetEncoding("GB2312"), contents)
+                   ?? TestCodePage(Encoding.GetEncoding("EUC-KR"), contents)
+                   ?? TestCodePage(Encoding.Unicode, contents)
+                   ?? TestCodePage(Encoding.BigEndianUnicode, contents)
+                   ?? TestCodePage(Encoding.ASCII, contents)
+                   ;
+        }
+    }
+}

File diff suppressed because it is too large
+ 1361 - 0
CueCli/Libs/CueSharp (1).cs


File diff suppressed because it is too large
+ 1422 - 0
CueCli/Libs/CueSharp.cs


+ 15 - 0
CueCli/Models/TrackModel.cs

@@ -0,0 +1,15 @@
+using System;
+
+namespace CueCli.Models
+{
+    public class TrackModel
+    {
+        public int TrackNo { get; set; }
+        public string Title { get; set; }
+
+        public string Performer { get; set; }
+
+        public TimeSpan Offset { get; set; }
+        public TimeSpan Duration { get; set; }
+    }
+}

+ 63 - 0
CueCli/Program.cs

@@ -0,0 +1,63 @@
+using CueCli;
+using CueCli.Helpers;
+using System;
+using System.IO;
+
+Console.WriteLine("Cue Command-line tool");
+
+if (args.Length != 3) Die(-1, $"Missing args.{Environment.NewLine}Usage: CueCli <input_audio_file> <input_cue_file> <output_dir>");
+
+var inputAudioFilePath = args[0];
+var inputCueFilePath = args[1];
+var outputDir = args[2];
+
+if (File.Exists(inputAudioFilePath) == false) Die(-2, "Input audio file not exist");
+var tag = TagLib.File.Create(inputAudioFilePath);
+var duration = tag.Properties.Duration;
+Console.WriteLine($"Audio file duration: {duration}");
+
+if (File.Exists(inputCueFilePath) == false) Die(-3, "Input cue file not exist");
+
+var cueBytes = File.ReadAllBytes(inputCueFilePath);
+var encoding = TextHelper.DetectCodePage(cueBytes);
+if (encoding == null) Die(-5, "Can not detect cue file text encoding");
+var cueText = encoding.GetString(cueBytes);
+var tracks = CueHelper.ParseCue(cueText, duration);
+Console.WriteLine($"Cue tracks number: {tracks.Length}");
+var numberPaddingFormat = "".PadLeft(tracks.Length.ToString().Length, '0');
+foreach (var trackModel in tracks)
+{
+    var d = trackModel.Duration;
+    Console.WriteLine($"  {trackModel.TrackNo.ToString(numberPaddingFormat)}. {d.TotalMinutes:00}:{d.Seconds:00}.{d.Milliseconds:000} {trackModel.Title} - {trackModel.Performer}");
+}
+
+if (Directory.Exists(outputDir) == false) Die(-6, "Error: Output dir not exist");
+
+Console.WriteLine();
+Console.WriteLine("Start Extracting...");
+
+foreach (var track in tracks)
+{
+    var outputFilePath = Path.Combine(
+        outputDir,
+        FilenameHelper.RemoveInvalidCharacters(
+            $"{track.TrackNo.ToString(numberPaddingFormat)}. {track.Title}{Path.GetExtension(inputAudioFilePath)}"
+        )
+    );
+
+    Console.WriteLine($"Output: {outputFilePath}");
+
+    var ffArgs = FfMpegHelper.CutSegment(inputAudioFilePath, outputFilePath, track.Offset, track.Duration);
+
+    FfMpegHelper.Launch(ffArgs).WaitForExit();
+}
+
+Console.WriteLine("Finished.");
+
+return 0;
+
+void Die(int exitCode, string errMessage = null)
+{
+    if (errMessage != null) Console.WriteLine($"Error: {errMessage}");
+    Environment.Exit(exitCode);
+}

+ 8 - 0
CueCli/Properties/launchSettings.json

@@ -0,0 +1,8 @@
+{
+  "profiles": {
+    "CueCli": {
+      "commandName": "Project",
+      "commandLineArgs": "\"Z:\\in\\toradora_drama_cd_2.m4a\" \"Z:\\in\\toradora_drama_cd_2.cue\" \"Z:\\CutOut\""
+    }
+  }
+}

+ 6 - 0
CueSplitter/App.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
+    </startup>
+</configuration>

+ 43 - 0
CueSplitter/CueProcessor.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using CueSplitter.Models;
+
+namespace CueSplitter
+{
+    internal class CueProcessor
+    {
+        public static TrackModel[] ParseCue(string cueText, TimeSpan totalDuration)
+        {
+            var cue = new CueSharp_N.CueSheet(cueText, (char[])null);
+
+            var convertedTracks = new List<TrackModel>(cue.Tracks.Length);
+
+            TrackModel lastTrk = null;
+            foreach (var track in cue.Tracks)
+            {
+                if (lastTrk != null)
+                {
+                    lastTrk.Duration = track.Offset - lastTrk.Offset;
+                }
+
+                var model = new TrackModel
+                {
+                    TrackNo = track.TrackNumber,
+                    Offset = track.Offset,
+                    Performer = track.Performer,
+                    Title = track.Title,
+                };
+
+                lastTrk = model;
+                convertedTracks.Add(model);
+            }
+
+            if (lastTrk != null)
+            {
+                lastTrk.Duration = totalDuration - lastTrk.Offset;
+            }
+
+            return convertedTracks.ToArray();
+        }
+    }
+}

+ 40 - 0
CueSplitter/CueSplitForm.Designer.cs

@@ -0,0 +1,40 @@
+
+namespace CueSplitter
+{
+    partial class CueSplitForm
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.components = new System.ComponentModel.Container();
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(800, 450);
+            this.Text = "CueSplitForm";
+        }
+
+        #endregion
+    }
+}

+ 20 - 0
CueSplitter/CueSplitForm.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace CueSplitter
+{
+    public partial class CueSplitForm : Form
+    {
+        public CueSplitForm()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 103 - 0
CueSplitter/CueSplitter.csproj

@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <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>{856A7773-9853-4008-9E15-6AD190587BD4}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>CueSplitter</RootNamespace>
+    <AssemblyName>CueSplitter</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+    <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="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>
+    </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.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+    <Reference Include="taglib-sharp, Version=2.1.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>C:\NuGetLocalRepo\taglib-sharp-netstandard2.0.2.1.0\lib\netstandard2.0\taglib-sharp.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="CueProcessor.cs" />
+    <Compile Include="CueSplitForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="CueSplitForm.Designer.cs">
+      <DependentUpon>CueSplitForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="CueSplitterMainForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="CueSplitterMainForm.Designer.cs">
+      <DependentUpon>CueSplitterMainForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Libs\CueSharp %281%29.cs" />
+    <Compile Include="Libs\CueSharp.cs" />
+    <Compile Include="Models\TrackModel.cs" />
+    <Compile Include="GuiProgram.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <EmbeddedResource Include="CueSplitterMainForm.resx">
+      <DependentUpon>CueSplitterMainForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+      <DesignTime>True</DesignTime>
+    </Compile>
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <ItemGroup />
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 295 - 0
CueSplitter/CueSplitterMainForm.Designer.cs

@@ -0,0 +1,295 @@
+
+namespace CueSplitter
+{
+    partial class CueSplitterMainForm
+    {
+        /// <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 Windows 窗体设计器生成的代码
+
+        /// <summary>
+        /// 设计器支持所需的方法 - 不要修改
+        /// 使用代码编辑器修改此方法的内容。
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.TracksListView = new System.Windows.Forms.ListView();
+            this.TrackColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+            this.TitleColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+            this.PerformerColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+            this.LengthColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+            this.MediaFileLabel = new System.Windows.Forms.Label();
+            this.MediaFileDropLabel = new System.Windows.Forms.Label();
+            this.CoverPictureBox = new System.Windows.Forms.PictureBox();
+            this.GoButton = new System.Windows.Forms.Button();
+            this.UpperSplitContainer = new System.Windows.Forms.SplitContainer();
+            this.OutputDirLabel = new System.Windows.Forms.Label();
+            this.OutputDirDropLabel = new System.Windows.Forms.Label();
+            this.MainSplitContainer = new System.Windows.Forms.SplitContainer();
+            this.LowerSplitContainer = new System.Windows.Forms.SplitContainer();
+            this.VgmDbButton = new System.Windows.Forms.Button();
+            this.DiscPropertyGrid = new System.Windows.Forms.PropertyGrid();
+            ((System.ComponentModel.ISupportInitialize)(this.CoverPictureBox)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.UpperSplitContainer)).BeginInit();
+            this.UpperSplitContainer.Panel1.SuspendLayout();
+            this.UpperSplitContainer.Panel2.SuspendLayout();
+            this.UpperSplitContainer.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.MainSplitContainer)).BeginInit();
+            this.MainSplitContainer.Panel1.SuspendLayout();
+            this.MainSplitContainer.Panel2.SuspendLayout();
+            this.MainSplitContainer.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.LowerSplitContainer)).BeginInit();
+            this.LowerSplitContainer.Panel1.SuspendLayout();
+            this.LowerSplitContainer.Panel2.SuspendLayout();
+            this.LowerSplitContainer.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // TracksListView
+            // 
+            this.TracksListView.AllowDrop = true;
+            this.TracksListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+            this.TrackColumnHeader,
+            this.TitleColumnHeader,
+            this.PerformerColumnHeader,
+            this.LengthColumnHeader});
+            this.TracksListView.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.TracksListView.FullRowSelect = true;
+            this.TracksListView.GridLines = true;
+            this.TracksListView.HideSelection = false;
+            this.TracksListView.Location = new System.Drawing.Point(0, 0);
+            this.TracksListView.Name = "TracksListView";
+            this.TracksListView.Size = new System.Drawing.Size(434, 320);
+            this.TracksListView.TabIndex = 0;
+            this.TracksListView.UseCompatibleStateImageBehavior = false;
+            this.TracksListView.View = System.Windows.Forms.View.Details;
+            this.TracksListView.DragDrop += new System.Windows.Forms.DragEventHandler(this.TracksListView_DragDrop);
+            this.TracksListView.DragEnter += new System.Windows.Forms.DragEventHandler(this.FileDragLink);
+            // 
+            // TrackColumnHeader
+            // 
+            this.TrackColumnHeader.Text = "Track No.";
+            this.TrackColumnHeader.Width = 82;
+            // 
+            // TitleColumnHeader
+            // 
+            this.TitleColumnHeader.Text = "Title";
+            this.TitleColumnHeader.Width = 180;
+            // 
+            // PerformerColumnHeader
+            // 
+            this.PerformerColumnHeader.Text = "Performer";
+            this.PerformerColumnHeader.Width = 90;
+            // 
+            // LengthColumnHeader
+            // 
+            this.LengthColumnHeader.Text = "Length";
+            // 
+            // MediaFileLabel
+            // 
+            this.MediaFileLabel.Location = new System.Drawing.Point(12, 12);
+            this.MediaFileLabel.Name = "MediaFileLabel";
+            this.MediaFileLabel.Size = new System.Drawing.Size(71, 21);
+            this.MediaFileLabel.TabIndex = 1;
+            this.MediaFileLabel.Text = "Media file:";
+            this.MediaFileLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
+            // 
+            // MediaFileDropLabel
+            // 
+            this.MediaFileDropLabel.AllowDrop = true;
+            this.MediaFileDropLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.MediaFileDropLabel.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
+            this.MediaFileDropLabel.Location = new System.Drawing.Point(102, 12);
+            this.MediaFileDropLabel.Name = "MediaFileDropLabel";
+            this.MediaFileDropLabel.Size = new System.Drawing.Size(619, 21);
+            this.MediaFileDropLabel.TabIndex = 1;
+            this.MediaFileDropLabel.Text = "<drag in to here>";
+            this.MediaFileDropLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
+            this.MediaFileDropLabel.DragDrop += new System.Windows.Forms.DragEventHandler(this.MediaFileDrop);
+            this.MediaFileDropLabel.DragEnter += new System.Windows.Forms.DragEventHandler(this.FileDragLink);
+            // 
+            // CoverPictureBox
+            // 
+            this.CoverPictureBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+            this.CoverPictureBox.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.CoverPictureBox.Location = new System.Drawing.Point(0, 0);
+            this.CoverPictureBox.Name = "CoverPictureBox";
+            this.CoverPictureBox.Size = new System.Drawing.Size(334, 320);
+            this.CoverPictureBox.TabIndex = 2;
+            this.CoverPictureBox.TabStop = false;
+            // 
+            // GoButton
+            // 
+            this.GoButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.GoButton.Location = new System.Drawing.Point(727, 12);
+            this.GoButton.Name = "GoButton";
+            this.GoButton.Size = new System.Drawing.Size(57, 53);
+            this.GoButton.TabIndex = 4;
+            this.GoButton.Text = "GO";
+            this.GoButton.UseVisualStyleBackColor = true;
+            // 
+            // UpperSplitContainer
+            // 
+            this.UpperSplitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.UpperSplitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel2;
+            this.UpperSplitContainer.Location = new System.Drawing.Point(0, 0);
+            this.UpperSplitContainer.Name = "UpperSplitContainer";
+            // 
+            // UpperSplitContainer.Panel1
+            // 
+            this.UpperSplitContainer.Panel1.Controls.Add(this.TracksListView);
+            // 
+            // UpperSplitContainer.Panel2
+            // 
+            this.UpperSplitContainer.Panel2.Controls.Add(this.CoverPictureBox);
+            this.UpperSplitContainer.Size = new System.Drawing.Size(772, 320);
+            this.UpperSplitContainer.SplitterDistance = 434;
+            this.UpperSplitContainer.TabIndex = 6;
+            // 
+            // OutputDirLabel
+            // 
+            this.OutputDirLabel.Location = new System.Drawing.Point(12, 45);
+            this.OutputDirLabel.Name = "OutputDirLabel";
+            this.OutputDirLabel.Size = new System.Drawing.Size(71, 20);
+            this.OutputDirLabel.TabIndex = 1;
+            this.OutputDirLabel.Text = "Output Dir:";
+            this.OutputDirLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
+            // 
+            // OutputDirDropLabel
+            // 
+            this.OutputDirDropLabel.AllowDrop = true;
+            this.OutputDirDropLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.OutputDirDropLabel.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
+            this.OutputDirDropLabel.Location = new System.Drawing.Point(102, 44);
+            this.OutputDirDropLabel.Name = "OutputDirDropLabel";
+            this.OutputDirDropLabel.Size = new System.Drawing.Size(619, 21);
+            this.OutputDirDropLabel.TabIndex = 1;
+            this.OutputDirDropLabel.Text = "<drag in to here>";
+            this.OutputDirDropLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
+            this.OutputDirDropLabel.DragDrop += new System.Windows.Forms.DragEventHandler(this.DirDropLabel);
+            this.OutputDirDropLabel.DragEnter += new System.Windows.Forms.DragEventHandler(this.DirDragLink);
+            // 
+            // MainSplitContainer
+            // 
+            this.MainSplitContainer.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.MainSplitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel2;
+            this.MainSplitContainer.Location = new System.Drawing.Point(14, 71);
+            this.MainSplitContainer.Name = "MainSplitContainer";
+            this.MainSplitContainer.Orientation = System.Windows.Forms.Orientation.Horizontal;
+            // 
+            // MainSplitContainer.Panel1
+            // 
+            this.MainSplitContainer.Panel1.Controls.Add(this.UpperSplitContainer);
+            // 
+            // MainSplitContainer.Panel2
+            // 
+            this.MainSplitContainer.Panel2.Controls.Add(this.LowerSplitContainer);
+            this.MainSplitContainer.Size = new System.Drawing.Size(772, 509);
+            this.MainSplitContainer.SplitterDistance = 320;
+            this.MainSplitContainer.TabIndex = 7;
+            // 
+            // LowerSplitContainer
+            // 
+            this.LowerSplitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.LowerSplitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel2;
+            this.LowerSplitContainer.Location = new System.Drawing.Point(0, 0);
+            this.LowerSplitContainer.Name = "LowerSplitContainer";
+            // 
+            // LowerSplitContainer.Panel1
+            // 
+            this.LowerSplitContainer.Panel1.Controls.Add(this.VgmDbButton);
+            // 
+            // LowerSplitContainer.Panel2
+            // 
+            this.LowerSplitContainer.Panel2.Controls.Add(this.DiscPropertyGrid);
+            this.LowerSplitContainer.Size = new System.Drawing.Size(772, 185);
+            this.LowerSplitContainer.SplitterDistance = 437;
+            this.LowerSplitContainer.TabIndex = 0;
+            // 
+            // VgmDbButton
+            // 
+            this.VgmDbButton.Location = new System.Drawing.Point(3, 3);
+            this.VgmDbButton.Name = "VgmDbButton";
+            this.VgmDbButton.Size = new System.Drawing.Size(75, 23);
+            this.VgmDbButton.TabIndex = 0;
+            this.VgmDbButton.Text = "VGMDB...";
+            this.VgmDbButton.UseVisualStyleBackColor = true;
+            // 
+            // DiscPropertyGrid
+            // 
+            this.DiscPropertyGrid.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.DiscPropertyGrid.Location = new System.Drawing.Point(0, 0);
+            this.DiscPropertyGrid.Name = "DiscPropertyGrid";
+            this.DiscPropertyGrid.Size = new System.Drawing.Size(331, 185);
+            this.DiscPropertyGrid.TabIndex = 0;
+            // 
+            // CueSplitterMainForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(796, 592);
+            this.Controls.Add(this.MainSplitContainer);
+            this.Controls.Add(this.GoButton);
+            this.Controls.Add(this.OutputDirDropLabel);
+            this.Controls.Add(this.OutputDirLabel);
+            this.Controls.Add(this.MediaFileDropLabel);
+            this.Controls.Add(this.MediaFileLabel);
+            this.Name = "CueSplitterMainForm";
+            this.Text = "CUE Splitter";
+            ((System.ComponentModel.ISupportInitialize)(this.CoverPictureBox)).EndInit();
+            this.UpperSplitContainer.Panel1.ResumeLayout(false);
+            this.UpperSplitContainer.Panel2.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.UpperSplitContainer)).EndInit();
+            this.UpperSplitContainer.ResumeLayout(false);
+            this.MainSplitContainer.Panel1.ResumeLayout(false);
+            this.MainSplitContainer.Panel2.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.MainSplitContainer)).EndInit();
+            this.MainSplitContainer.ResumeLayout(false);
+            this.LowerSplitContainer.Panel1.ResumeLayout(false);
+            this.LowerSplitContainer.Panel2.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.LowerSplitContainer)).EndInit();
+            this.LowerSplitContainer.ResumeLayout(false);
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.ListView TracksListView;
+        private System.Windows.Forms.ColumnHeader TrackColumnHeader;
+        private System.Windows.Forms.ColumnHeader TitleColumnHeader;
+        private System.Windows.Forms.ColumnHeader PerformerColumnHeader;
+        private System.Windows.Forms.Label MediaFileLabel;
+        private System.Windows.Forms.Label MediaFileDropLabel;
+        private System.Windows.Forms.PictureBox CoverPictureBox;
+        private System.Windows.Forms.Button GoButton;
+        private System.Windows.Forms.SplitContainer UpperSplitContainer;
+        private System.Windows.Forms.Label OutputDirLabel;
+        private System.Windows.Forms.Label OutputDirDropLabel;
+        private System.Windows.Forms.SplitContainer MainSplitContainer;
+        private System.Windows.Forms.SplitContainer LowerSplitContainer;
+        private System.Windows.Forms.PropertyGrid DiscPropertyGrid;
+        private System.Windows.Forms.Button VgmDbButton;
+        private System.Windows.Forms.ColumnHeader LengthColumnHeader;
+    }
+}
+

+ 134 - 0
CueSplitter/CueSplitterMainForm.cs

@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Windows.Forms;
+using CueSplitter.Models;
+
+namespace CueSplitter
+{
+    public partial class CueSplitterMainForm : Form
+    {
+        private string _selectedMediaFilePath;
+        private TimeSpan _selectedMediaFileDuration;
+
+        public CueSplitterMainForm()
+        {
+            InitializeComponent();
+        }
+
+        private void FileDragLink(object sender, DragEventArgs e)
+        {
+            if (e.Data.GetDataPresent(DataFormats.FileDrop)
+                && e.Data.GetData(DataFormats.FileDrop) is string[] files
+                && files.Length == 1
+                && File.Exists(files[0])
+            )
+            {
+                e.Effect = DragDropEffects.Link;
+            }
+        }
+
+        private void MediaFileDrop(object sender, DragEventArgs e)
+        {
+            if (sender is Label self
+                && e.Data.GetDataPresent(DataFormats.FileDrop)
+                && e.Data.GetData(DataFormats.FileDrop) is string[] files
+                && files.Length == 1
+                && File.Exists(files[0])
+            )
+            {
+                var tag = TagLib.File.Create(files[0]);
+
+                _selectedMediaFileDuration = tag.Properties.Duration;
+                _selectedMediaFilePath = files[0];
+
+                self.Text = $"{files[0]} ({_selectedMediaFileDuration:g})";
+            }
+        }
+
+        private void DirDragLink(object sender, DragEventArgs e)
+        {
+            if (e.Data.GetDataPresent(DataFormats.FileDrop)
+                && e.Data.GetData(DataFormats.FileDrop) is string[] files
+                && files.Length == 1
+                && Directory.Exists(files[0])
+            )
+            {
+                e.Effect = DragDropEffects.Link;
+            }
+        }
+
+        private void DirDropLabel(object sender, DragEventArgs e)
+        {
+            if (sender is Label self
+                && e.Data.GetDataPresent(DataFormats.FileDrop)
+                && e.Data.GetData(DataFormats.FileDrop) is string[] files
+                && files.Length == 1
+                && Directory.Exists(files[0])
+            )
+            {
+                self.Text = files[0];
+            }
+        }
+
+        private void TracksListView_DragDrop(object sender, DragEventArgs e)
+        {
+            if (sender is ListView self
+                && e.Data.GetDataPresent(DataFormats.FileDrop)
+                && e.Data.GetData(DataFormats.FileDrop) is string[] files
+                && files.Length == 1
+                && File.Exists(files[0])
+            )
+            {
+                if (_selectedMediaFilePath == null)
+                {
+                    MessageBox.Show("Choose a media file first.");
+                    return;
+                }
+
+                var text = File.ReadAllText(files[0]);
+                var cue = new CueSharp_N.CueSheet(text, (char[])null);
+
+                var convertedTracks = new List<TrackModel>(cue.Tracks.Length);
+
+                TrackModel lastTrk = null;
+                foreach (var track in cue.Tracks)
+                {
+                    if (lastTrk != null)
+                    {
+                        lastTrk.Duration = track.Offset - lastTrk.Offset;
+                    }
+
+                    var model = new TrackModel
+                    {
+                        TrackNo = track.TrackNumber,
+                        Offset = track.Offset,
+                        Performer = track.Performer,
+                        Title = track.Title,
+                    };
+
+                    lastTrk = model;
+                    convertedTracks.Add(model);
+                }
+
+                if (lastTrk != null)
+                {
+                    lastTrk.Duration = _selectedMediaFileDuration - lastTrk.Offset;
+                }
+
+                self.Items.Clear();
+                
+                foreach (var item in convertedTracks)
+                {
+                    var lvi = new ListViewItem(item.TrackNo.ToString());
+                    lvi.SubItems.Add(item.Title);
+                    lvi.SubItems.Add(item.Performer);
+                    lvi.SubItems.Add(item.Duration.ToString("g"));
+                    lvi.Tag = item;
+                    self.Items.Add(lvi);
+                }
+
+            }
+        }
+    }
+}

+ 120 - 0
CueSplitter/CueSplitterMainForm.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=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 22 - 0
CueSplitter/GuiProgram.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace CueSplitter
+{
+    static class GuiProgram
+    {
+        /// <summary>
+        /// 应用程序的主入口点。
+        /// </summary>
+        [STAThread]
+        static void MainGui()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            Application.Run(new CueSplitterMainForm());
+        }
+    }
+}

File diff suppressed because it is too large
+ 1361 - 0
CueSplitter/Libs/CueSharp (1).cs


File diff suppressed because it is too large
+ 1422 - 0
CueSplitter/Libs/CueSharp.cs


+ 15 - 0
CueSplitter/Models/TrackModel.cs

@@ -0,0 +1,15 @@
+using System;
+
+namespace CueSplitter.Models
+{
+    public class TrackModel
+    {
+        public int TrackNo { get; set; }
+        public string Title { get; set; }
+
+        public string Performer { get; set; }
+
+        public TimeSpan Offset { get; set; }
+        public TimeSpan Duration { get; set; }
+    }
+}

+ 13 - 0
CueSplitter/Program.cs

@@ -0,0 +1,13 @@
+using System.Diagnostics;
+
+namespace CueSplitter
+{
+    internal class Program
+    {
+        static void Main(string[] args)
+        {
+            
+        }
+
+    }
+}

+ 36 - 0
CueSplitter/Properties/AssemblyInfo.cs

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

+ 63 - 0
CueSplitter/Properties/Resources.Designer.cs

@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     此代码由工具生成。
+//     运行时版本:4.0.30319.42000
+//
+//     对此文件的更改可能会导致不正确的行为,并且如果
+//     重新生成代码,这些更改将会丢失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace CueSplitter.Properties {
+    using System;
+    
+    
+    /// <summary>
+    ///   一个强类型的资源类,用于查找本地化的字符串等。
+    /// </summary>
+    // 此类是由 StronglyTypedResourceBuilder
+    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+    // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+    // (以 /str 作为命令选项),或重新生成 VS 项目。
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   返回此类使用的缓存的 ResourceManager 实例。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CueSplitter.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   重写当前线程的 CurrentUICulture 属性,对
+        ///   使用此强类型资源类的所有资源查找执行重写。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+    }
+}

+ 117 - 0
CueSplitter/Properties/Resources.resx

@@ -0,0 +1,117 @@
+<?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.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: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" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </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" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </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>

+ 26 - 0
CueSplitter/Properties/Settings.Designer.cs

@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     此代码由工具生成。
+//     运行时版本:4.0.30319.42000
+//
+//     对此文件的更改可能会导致不正确的行为,并且如果
+//     重新生成代码,这些更改将会丢失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace CueSplitter.Properties {
+    
+    
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.10.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+        
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+        
+        public static Settings Default {
+            get {
+                return defaultInstance;
+            }
+        }
+    }
+}

+ 7 - 0
CueSplitter/Properties/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 5 - 0
CueSplitter/packages.config

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="taglib" version="2.1.0.0" targetFramework="net45" />
+  <package id="taglib-sharp-netstandard2.0" version="2.1.0" targetFramework="net472" />
+</packages>

+ 13 - 1
MusicTool.sln

@@ -14,7 +14,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "@", "@", "{1F900AE3-F1C6-4C
 		NuGet.config = NuGet.config
 	EndProjectSection
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BatchProcess", "BatchProcess\BatchProcess.csproj", "{165C4021-2755-49B9-AB25-3EDAB449E2D5}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BatchProcess", "BatchProcess\BatchProcess.csproj", "{165C4021-2755-49B9-AB25-3EDAB449E2D5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CueSplitter", "CueSplitter\CueSplitter.csproj", "{856A7773-9853-4008-9E15-6AD190587BD4}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CueCli", "CueCli\CueCli.csproj", "{E91FEF19-665C-45F6-BE5D-97F6C8D46F25}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -38,6 +42,14 @@ Global
 		{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
+		{856A7773-9853-4008-9E15-6AD190587BD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{856A7773-9853-4008-9E15-6AD190587BD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{856A7773-9853-4008-9E15-6AD190587BD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{856A7773-9853-4008-9E15-6AD190587BD4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E91FEF19-665C-45F6-BE5D-97F6C8D46F25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E91FEF19-665C-45F6-BE5D-97F6C8D46F25}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E91FEF19-665C-45F6-BE5D-97F6C8D46F25}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E91FEF19-665C-45F6-BE5D-97F6C8D46F25}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE