diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 2635b13ab..658b67ed7 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -34,6 +34,7 @@ jobs: git apply --directory=ThirdParty/flac ThirdParty/submodule_flac_CUETools.patch --verbose --whitespace=nowarn powershell -c "Expand-Archive ThirdParty/MAC_SDK/MAC_1086_SDK.zip -DestinationPath ThirdParty/MAC_SDK/" git apply --directory=ThirdParty/MAC_SDK ThirdParty/ThirdParty_MAC_SDK_CUETools.patch --verbose + git apply --directory=ThirdParty/openclnet ThirdParty/submodule_openclnet_CUETools.patch --verbose git apply --directory=ThirdParty/taglib-sharp ThirdParty/submodule_taglib-sharp_CUETools.patch --verbose git apply --directory=ThirdParty/WavPack ThirdParty/submodule_WavPack_CUETools.patch --verbose git apply --directory=ThirdParty/WindowsMediaLib ThirdParty/submodule_WindowsMediaLib_CUETools.patch --verbose @@ -43,6 +44,9 @@ jobs: - name: Build Release|Any CPU run: | "%DEVENV_PATH%" "%SLN_PATH%" /Build "Release|Any CPU" + - name: Test AccurateRip Meta + run: | + dotnet test CUETools\CUETools.TestProcessor\CUETools.TestProcessor.csproj --configuration Release --filter AccurateRipMetaTest -v:minimal - name: Build Release|x64 run: | "%DEVENV_PATH%" "%SLN_PATH%" /Build "Release|x64" diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index fd3284e87..1ac807101 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -35,6 +35,7 @@ jobs: git apply --directory=ThirdParty/flac ThirdParty/submodule_flac_CUETools.patch --verbose --whitespace=nowarn powershell -c "Expand-Archive ThirdParty/MAC_SDK/MAC_1086_SDK.zip -DestinationPath ThirdParty/MAC_SDK/" git apply --directory=ThirdParty/MAC_SDK ThirdParty/ThirdParty_MAC_SDK_CUETools.patch --verbose + git apply --directory=ThirdParty/openclnet ThirdParty/submodule_openclnet_CUETools.patch --verbose git apply --directory=ThirdParty/taglib-sharp ThirdParty/submodule_taglib-sharp_CUETools.patch --verbose git apply --directory=ThirdParty/WavPack ThirdParty/submodule_WavPack_CUETools.patch --verbose git apply --directory=ThirdParty/WindowsMediaLib ThirdParty/submodule_WindowsMediaLib_CUETools.patch --verbose diff --git a/Bwg.Hardware/Bwg.Hardware.csproj b/Bwg.Hardware/Bwg.Hardware.csproj index 433d3353d..af04ddaae 100644 --- a/Bwg.Hardware/Bwg.Hardware.csproj +++ b/Bwg.Hardware/Bwg.Hardware.csproj @@ -1,7 +1,7 @@  - net47;net20 + netstandard2.1 0.0.7.1 Bwg.Hardware Bwg.Hardware @@ -16,6 +16,10 @@ https://svn.code.sf.net/p/bwgburn/code/ svn BwgSoftware - - - + + + + + + + diff --git a/Bwg.Logging/Bwg.Logging.csproj b/Bwg.Logging/Bwg.Logging.csproj index 0527ed5f1..197f336a9 100644 --- a/Bwg.Logging/Bwg.Logging.csproj +++ b/Bwg.Logging/Bwg.Logging.csproj @@ -1,7 +1,7 @@  - net47;net20 + netstandard2.1 0.0.7.1 Bwg.Logging Bwg.Logging diff --git a/Bwg.Scsi/Bwg.Scsi.csproj b/Bwg.Scsi/Bwg.Scsi.csproj index 87a3b9b77..b58b90fe5 100644 --- a/Bwg.Scsi/Bwg.Scsi.csproj +++ b/Bwg.Scsi/Bwg.Scsi.csproj @@ -1,7 +1,7 @@  - net47;net20 + netstandard2.1 0.0.7.1 Bwg.Scsi Bwg.Scsi diff --git a/CUEControls/CUEControls.csproj b/CUEControls/CUEControls.csproj index e5cfe4453..452c8316e 100644 --- a/CUEControls/CUEControls.csproj +++ b/CUEControls/CUEControls.csproj @@ -1,23 +1,22 @@  - net47;net20 + net10.0-windows 2.2.6.0 CUEControls - CUEControls - CUETools + CUEControls + 7.0 + CUETools Library containing windows GUI controls for CUETools. Copyright (c) 2008-2025 Grigory Chudov Grigory Chudov - true + true + true + $(NoWarn);WFO1000 ..\bin\$(Configuration)\ https://github.com/gchudov/cuetools.net git - - - - - + diff --git a/CUEPlayer/CUEPlayer.csproj b/CUEPlayer/CUEPlayer.csproj index 4a6283749..af48efe6b 100644 --- a/CUEPlayer/CUEPlayer.csproj +++ b/CUEPlayer/CUEPlayer.csproj @@ -1,296 +1,46 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {04E59836-0C5A-4B9B-8899-848D56911758} - WinExe - Properties - CUEPlayer - CUEPlayer - v4.7 - 512 - false - - - 3.5 - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - - true - full - false - ..\bin\Debug\net47\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - ..\bin\Release\net47\ - TRACE - prompt - 4 - true - AllRules.ruleset - false - - - - - - - - - - - - - - Form - - - Browser.cs - - - DataSet1.xsd - - - Form - - - Deck.cs - - - Form - - - frmCUEPlayer.cs - - - Form - - - Icecast.cs - - - Form - - - IcecastSettings.cs - - - Form - - - Output.cs - - - Form - - - Playlist.cs - - - - - Browser.cs - - - Deck.cs - - - frmCUEPlayer.cs - - - Icecast.cs - - - IcecastSettings.cs - - - Output.cs - - - Playlist.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - True - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - {CA4D64E6-6544-4A29-8BA5-7DB08D50D072} - CUEControls - False - - - {1DD41038-D885-46C5-8DDE-E0B82F066584} - CUETools.CDImage - False - - - {FAD09EE2-D6B2-4A8E-9F1C-2A9FB293661A} - CUETools.Codecs.CoreAudio - - - {8FC5DA7C-F6AC-4D04-85BC-1233DDF569E7} - CUETools.Codecs.Icecast - - - {6458A13A-30EF-45A9-9D58-E5031B17BEE2} - CUETools.Codecs - False - - - {AFF1AFF9-839D-4892-88CD-8BD09BAFC1D2} - CUETools.DSP.Mixer - - - {A6303861-CA06-4C2C-A104-BA9291538F6F} - CUETools.DSP.Resampler - - - {4911BD82-49EF-4858-8B51-5394F86739A4} - CUETools.Processor - False - - - {8DD1E84B-0B03-4C0B-9B42-1E49F75E7CB1} - ProgressODoom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DataSet1.xsd - - - Designer - MSDataSetGenerator - - - DataSet1.xsd - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 2.0 %28x86%29 - true - - - False - .NET Framework 3.0 %28x86%29 - false - - - False - .NET Framework 3.5 - false - - - False - .NET Framework 3.5 SP1 - false - - - False - SQL Server Compact 3.5 SP2 - true - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + net10.0-windows + WinExe + CUEPlayer + CUEPlayer + 7.0 + true + false + true + $(NoWarn);WFO1000 + ..\bin\$(Configuration)\ + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + PreserveNewest + + + + diff --git a/CUEPlayer/DataSet1.cs b/CUEPlayer/DataSet1.cs index cbf14c209..d10133fc8 100644 --- a/CUEPlayer/DataSet1.cs +++ b/CUEPlayer/DataSet1.cs @@ -1,9 +1,229 @@ -namespace CUEPlayer { - - - public partial class DataSet1 { - partial class PlaylistDataTable - { - } - } -} +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; + +namespace CUEPlayer { + + + public partial class DataSet1 : DataSet { + private readonly PlaylistDataTable tablePlaylist; + + public DataSet1() + { + DataSetName = "DataSet1"; + tablePlaylist = new PlaylistDataTable(); + Tables.Add(tablePlaylist); + } + + public PlaylistDataTable Playlist + { + get { return tablePlaylist; } + } + + public partial class PlaylistDataTable : DataTable + { + private readonly DataColumn columnId; + private readonly DataColumn columnPath; + private readonly DataColumn columnArtist; + private readonly DataColumn columnTitle; + private readonly DataColumn columnAlbum; + private readonly DataColumn columnLength; + private readonly DataColumn columnTrack; + + public PlaylistDataTable() + : base("Playlist") + { + columnId = Columns.Add("id", typeof(int)); + columnId.AutoIncrement = true; + columnId.AutoIncrementSeed = -1; + columnId.AutoIncrementStep = -1; + columnPath = Columns.Add("path", typeof(string)); + columnArtist = Columns.Add("artist", typeof(string)); + columnTitle = Columns.Add("title", typeof(string)); + columnAlbum = Columns.Add("album", typeof(string)); + columnLength = Columns.Add("length", typeof(int)); + columnTrack = Columns.Add("track", typeof(int)); + PrimaryKey = new[] { columnId }; + } + + public PlaylistRow this[int index] + { + get { return (PlaylistRow)Rows[index]; } + } + + public PlaylistRow AddPlaylistRow(string path, string artist, string title, string album, int length, int track) + { + PlaylistRow row = (PlaylistRow)NewRow(); + row.path = path; + row.artist = artist; + row.title = title; + row.album = album; + row.length = length; + row.track = track; + Rows.Add(row); + return row; + } + + public IEnumerator GetEnumerator() + { + foreach (DataRow row in Rows) + if (row.RowState != DataRowState.Deleted) + yield return (PlaylistRow)row; + } + + protected override Type GetRowType() + { + return typeof(PlaylistRow); + } + + protected override DataRow NewRowFromBuilder(DataRowBuilder builder) + { + return new PlaylistRow(builder); + } + } + + public partial class PlaylistRow : DataRow + { + internal PlaylistRow(DataRowBuilder builder) + : base(builder) + { + } + + public int id + { + get { return FieldValue("id"); } + set { this["id"] = value; } + } + + public string path + { + get { return FieldValue("path"); } + set { this["path"] = value ?? (object)DBNull.Value; } + } + + public string artist + { + get { return FieldValue("artist"); } + set { this["artist"] = value ?? (object)DBNull.Value; } + } + + public string title + { + get { return FieldValue("title"); } + set { this["title"] = value ?? (object)DBNull.Value; } + } + + public string album + { + get { return FieldValue("album"); } + set { this["album"] = value ?? (object)DBNull.Value; } + } + + public int length + { + get { return FieldValue("length"); } + set { this["length"] = value; } + } + + public int track + { + get { return FieldValue("track"); } + set { this["track"] = value; } + } + + private T FieldValue(string columnName) + { + object value = this[columnName]; + if (value == DBNull.Value) + return default(T); + return (T)value; + } + } + } + + namespace DataSet1TableAdapters + { + public class PlaylistTableAdapter + { + private static string PlaylistFile + { + get + { + return Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "CUEPlayer", + "playlist.xml"); + } + } + + public int Fill(DataSet1.PlaylistDataTable table) + { + table.Clear(); + if (!File.Exists(PlaylistFile)) + return 0; + + DataSet dataSet = new DataSet(); + dataSet.ReadXml(PlaylistFile); + if (!dataSet.Tables.Contains("Playlist")) + return 0; + + int count = 0; + foreach (DataRow row in dataSet.Tables["Playlist"].Rows) + { + DataSet1.PlaylistRow playlistRow = table.AddPlaylistRow( + ReadString(row, "path"), + ReadString(row, "artist"), + ReadString(row, "title"), + ReadString(row, "album"), + ReadInt32(row, "length"), + ReadInt32(row, "track")); + if (dataSet.Tables["Playlist"].Columns.Contains("id")) + playlistRow.id = ReadInt32(row, "id"); + count++; + } + table.AcceptChanges(); + return count; + } + + public int Update(DataSet1.PlaylistDataTable table) + { + string directory = Path.GetDirectoryName(PlaylistFile); + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + + DataTable copy = table.Clone(); + foreach (DataRow row in table.Rows) + { + if (row.RowState == DataRowState.Deleted) + continue; + DataRow newRow = copy.NewRow(); + foreach (DataColumn column in table.Columns) + newRow[column.ColumnName] = row[column, DataRowVersion.Current]; + copy.Rows.Add(newRow); + } + + DataSet dataSet = new DataSet("DataSet1"); + dataSet.Tables.Add(copy); + dataSet.WriteXml(PlaylistFile, XmlWriteMode.WriteSchema); + int count = copy.Rows.Count; + table.AcceptChanges(); + return count; + } + + private static string ReadString(DataRow row, string columnName) + { + if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value) + return null; + return Convert.ToString(row[columnName]); + } + + private static int ReadInt32(DataRow row, string columnName) + { + if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value) + return 0; + return Convert.ToInt32(row[columnName]); + } + } + } +} diff --git a/CUEPlayer/Deck.cs b/CUEPlayer/Deck.cs index 1ec726218..d001a4392 100644 --- a/CUEPlayer/Deck.cs +++ b/CUEPlayer/Deck.cs @@ -1,11 +1,12 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Text; -using System.Threading; -using System.Windows.Forms; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Text; +using System.Threading; +using System.Windows.Forms; using CUETools.Codecs; using CUETools.Processor; using CUETools.DSP.Mixer; @@ -73,7 +74,7 @@ internal void UpdateDeck() textBoxAlbum.Text = playingRow < 0 ? "" : dataSet.Playlist[playingRow].album; textBoxTitle.Text = playingRow < 0 ? "" : dataSet.Playlist[playingRow].title; textBoxDuration.Text = ""; - pictureBox.Image = playingCue != null && playingCue.Cover != null ? playingCue.Cover : pictureBox.InitialImage; + pictureBox.Image = GetCoverImage(playingCue) ?? pictureBox.InitialImage; if (nextDeck != null && nextDeck.playingSource == null && playingRow >= 0 && playingRow < dataSet.Playlist.Rows.Count - 1) { @@ -88,8 +89,8 @@ internal void UpdateDeck() mediaSlider.Value = (int)(playingSource.Position - playingStart); } - private void mediaSliderA_ValueChanged(object sender, EventArgs e) - { + private void mediaSliderA_ValueChanged(object sender, EventArgs e) + { if (mediaSlider.Maximum == 1) return; TimeSpan len1 = TimeSpan.FromSeconds(mediaSlider.Maximum / 44100.0); TimeSpan len2 = TimeSpan.FromSeconds(mediaSlider.Value / 44100.0); @@ -97,10 +98,24 @@ private void mediaSliderA_ValueChanged(object sender, EventArgs e) string lenStr2 = string.Format("{0:d}.{1:d2}:{2:d2}:{3:d2}", len2.Days, len2.Hours, len2.Minutes, len2.Seconds).TrimStart('0', ':', '.'); lenStr1 = "0:00".Substring(0, Math.Max(0, 4 - lenStr1.Length)) + lenStr1; lenStr2 = "0:00".Substring(0, Math.Max(0, 4 - lenStr2.Length)) + lenStr2; - textBoxDuration.Text = lenStr2 + " / " + lenStr1; - } - - private int seekTo = -1; + textBoxDuration.Text = lenStr2 + " / " + lenStr1; + } + + private static Image GetCoverImage(CUESheet cueSheet) + { + if (cueSheet == null || cueSheet.AlbumArt == null) + return null; + var picture = cueSheet.AlbumArt.Find(x => x.Type == TagLib.PictureType.FrontCover) ?? + cueSheet.AlbumArt.Find(x => x.Type != TagLib.PictureType.NotAPicture); + if (picture == null) + return null; + using (MemoryStream imageStream = new MemoryStream(picture.Data.Data, 0, picture.Data.Count)) + try { return Image.FromStream(imageStream); } + catch { } + return null; + } + + private int seekTo = -1; private bool stopNow = false; private void PlayThread() diff --git a/CUEPlayer/Properties/Resources.Designer.cs b/CUEPlayer/Properties/Resources.Designer.cs index 5d5c3efe9..c27069a65 100644 --- a/CUEPlayer/Properties/Resources.Designer.cs +++ b/CUEPlayer/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace CUEPlayer.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/CUEPlayer/Properties/Settings.Designer.cs b/CUEPlayer/Properties/Settings.Designer.cs index ab8462056..3500a56d0 100644 --- a/CUEPlayer/Properties/Settings.Designer.cs +++ b/CUEPlayer/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace CUEPlayer.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "18.6.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/CUEPlayer/app.config b/CUEPlayer/app.config index 26cdf92ef..b22c675f1 100644 --- a/CUEPlayer/app.config +++ b/CUEPlayer/app.config @@ -11,6 +11,6 @@ - + diff --git a/CUERipper.WPF/CUERipper.WPF.csproj b/CUERipper.WPF/CUERipper.WPF.csproj index ffdd9999c..f51ac2d4d 100644 --- a/CUERipper.WPF/CUERipper.WPF.csproj +++ b/CUERipper.WPF/CUERipper.WPF.csproj @@ -1,159 +1,39 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {2D8D7A03-DFFA-44EF-9299-8BB56D122245} + net10.0-windows WinExe - Properties - CUERipper.WPF CUERipper.WPF - v4.7 - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - - - - - - - 3.5 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - true - full - false - ..\bin\Debug\net47\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - ..\bin\Release\net47\ - TRACE - prompt - 4 - false + CUERipper.WPF + 7.0 + true + true + false + ..\bin\$(Configuration)\ + - - 3.0 - - - 3.0 - - - - 3.5 - - - - - 3.5 - - - 3.5 - - - - + + + + - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - App.xaml - Code - - - Window1.xaml - Code - + + + + + - - Code - - - True - True - Resources.resx - - - True - Settings.settings - True - - + ResXFileCodeGenerator Resources.Designer.cs - - + SettingsSingleFileGenerator Settings.Designer.cs - - - - - {CA4D64E6-6544-4A29-8BA5-7DB08D50D072} - CUEControls - - - {1DD41038-D885-46C5-8DDE-E0B82F066584} - CUETools.CDImage - - - {4911BD82-49EF-4858-8B51-5394F86739A4} - CUETools.Processor - - - - - - - - - - - False - .NET Framework 3.5 SP1 - true - - - - \ No newline at end of file + + diff --git a/CUERipper.WPF/Properties/Resources.Designer.cs b/CUERipper.WPF/Properties/Resources.Designer.cs index 4fd0e16b7..6f4dfdb91 100644 --- a/CUERipper.WPF/Properties/Resources.Designer.cs +++ b/CUERipper.WPF/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace CUERipper.WPF.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/CUERipper.WPF/Properties/Settings.Designer.cs b/CUERipper.WPF/Properties/Settings.Designer.cs index 6269b8287..008c3faaa 100644 --- a/CUERipper.WPF/Properties/Settings.Designer.cs +++ b/CUERipper.WPF/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace CUERipper.WPF.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "18.6.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/CUERipper.WPF/app.config b/CUERipper.WPF/app.config index 545e8d3eb..220b02fab 100644 --- a/CUERipper.WPF/app.config +++ b/CUERipper.WPF/app.config @@ -1,6 +1,6 @@ - + diff --git a/CUERipper/CUERipper.csproj b/CUERipper/CUERipper.csproj index 0fe5a3cfe..2109250b3 100644 --- a/CUERipper/CUERipper.csproj +++ b/CUERipper/CUERipper.csproj @@ -1,278 +1,44 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {39B43BBB-BAFC-4D85-9BEA-3BCB7EFED89C} - WinExe - Properties - CUERipper - CUERipper - - - 3.5 - - - cue2.ico - F89503BB83CA42A647C506D67956D6F32C1C6E7E - CUERipper_TemporaryKey.pfx - false - false - true - false - - - LocalIntranet - Properties\app.manifest - v4.7 - publish\ - true - Web - true - Background - 7 - Days - false - false - true - http://www.cuetools.net/install/cueripper/ - http://www.cuetools.net/ - CUERipper - Gregory S. Chudov - true - index.html - 0 - 1.0.0.%2a - false - true - true - - - - true - full - false - ..\bin\Debug\net47\ - DEBUG;TRACE - prompt - 4 - true - AllRules.ruleset - - - pdbonly - true - ..\bin\Release\net47\ - TRACE - prompt - 4 - AllRules.ruleset - false - - - - - - - - - - - - - - Form - - - frmCUERipper.cs - - - Form - - - frmFreedbSubmit.cs - - - Form - - - Options.cs - - - - - frmCUERipper.cs - - - Designer - frmCUERipper.cs - - - frmCUERipper.cs - - - frmFreedbSubmit.cs - - - frmFreedbSubmit.cs - - - frmFreedbSubmit.cs - - - Options.cs - - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - True - Resources.resx - True - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - {CA4D64E6-6544-4A29-8BA5-7DB08D50D072} - CUEControls - - - {5802C7E9-157E-4124-946D-70B5AE48A5A1} - CUETools.AccurateRip - - - {1DD41038-D885-46C5-8DDE-E0B82F066584} - CUETools.CDImage - - - {6458A13A-30EF-45A9-9D58-E5031B17BEE2} - CUETools.Codecs - - - {c36efaa5-4f81-4a29-800d-ba866ff13bc8} - CUETools.CTDB.Types - - - {AA2A9A7E-45FB-4632-AD85-85B0E556F818} - CUETools.CTDB - - - {4911BD82-49EF-4858-8B51-5394F86739A4} - CUETools.Processor - - - {D2700165-3E77-4B28-928D-551F5FC11954} - CUETools.Ripper - - - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4} - Freedb - - - {8DD1E84B-0B03-4C0B-9B42-1E49F75E7CB1} - ProgressODoom - - - {1219a514-d3fa-40db-bbb2-92ce05e35839} - TaglibSharp - - - - - - - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 2.0 %28x86%29 - true - - - False - .NET Framework 3.0 %28x86%29 - false - - - False - .NET Framework 3.5 - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Visual C++ Runtime Libraries %28x64%29 - true - - - False - Visual C++ Runtime Libraries %28x86%29 - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + net10.0-windows + 2.2.6.0 + WinExe + CUERipper + CUERipper + cue2.ico + Properties\app.manifest + 7.0 + true + false + true + $(NoWarn);WFO1000 + ..\bin\$(Configuration)\ + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + diff --git a/CUERipper/Options.cs b/CUERipper/Options.cs index 77f623638..888a8922e 100644 --- a/CUERipper/Options.cs +++ b/CUERipper/Options.cs @@ -91,7 +91,9 @@ public CUERipperSettings(CUEConfig config) [DefaultValue(false), Category("CTDB"), DisplayName("Detailed log")] public bool DetailedCTDBLog { get { return config.advanced.DetailedCTDBLog; } set { config.advanced.DetailedCTDBLog = value; } } - [DefaultValue("gnudb.gnudb.org"), Category("Various"), DisplayName("Freedb site address")] - public string FreedbSiteAddress { get { return config.advanced.FreedbSiteAddress ; } set { config.advanced.FreedbSiteAddress = value; } } +#if DORMANT_FREEDB + [DefaultValue("gnudb.gnudb.org"), Category("Various"), DisplayName("Freedb site address")] + public string FreedbSiteAddress { get { return config.advanced.FreedbSiteAddress ; } set { config.advanced.FreedbSiteAddress = value; } } +#endif } } diff --git a/CUERipper/Program.cs b/CUERipper/Program.cs index 0e56cf1e5..69efe3e50 100644 --- a/CUERipper/Program.cs +++ b/CUERipper/Program.cs @@ -1,9 +1,8 @@ -using System; -using System.Deployment.Application; -using System.Globalization; -using System.Runtime.InteropServices; -using System.Threading; -using System.Windows.Forms; +using System; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Threading; +using System.Windows.Forms; using CUETools.Processor; using CUETools.Processor.Settings; @@ -31,26 +30,10 @@ static void Main() Application.Run(new frmCUERipper()); } - static void GetSatelliteAssemblies(string groupName) - { - if (ApplicationDeployment.IsNetworkDeployed) - { - ApplicationDeployment deploy = ApplicationDeployment.CurrentDeployment; - - if (deploy.IsFirstRun) - { - try - { - deploy.DownloadFileGroup(groupName); - } - catch (DeploymentException) - { - // Log error. Do not report this error to the user, because a satellite - // assembly may not exist if the user's culture and the application's - // default culture match. - } - } - } - } - } -} \ No newline at end of file + static void GetSatelliteAssemblies(string groupName) + { + // System.Deployment.Application is not available on .NET 10. + // Optional plugin file groups must be included by the publish/package step. + } + } +} diff --git a/CUERipper/Properties/Resources.Designer.cs b/CUERipper/Properties/Resources.Designer.cs index 18cec7edc..8b296b685 100644 --- a/CUERipper/Properties/Resources.Designer.cs +++ b/CUERipper/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace CUERipper.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/CUERipper/Properties/Settings.Designer.cs b/CUERipper/Properties/Settings.Designer.cs index ec1ef4e54..696701e66 100644 --- a/CUERipper/Properties/Settings.Designer.cs +++ b/CUERipper/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace CUERipper.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "18.6.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/CUERipper/app.config b/CUERipper/app.config index b979734e5..8f8552c04 100644 --- a/CUERipper/app.config +++ b/CUERipper/app.config @@ -8,6 +8,6 @@ - + diff --git a/CUERipper/frmCUERipper.cs b/CUERipper/frmCUERipper.cs index 499e8d060..e3fb9bfb6 100644 --- a/CUERipper/frmCUERipper.cs +++ b/CUERipper/frmCUERipper.cs @@ -2,20 +2,23 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; -using System.Drawing; -using System.IO; -using System.Net; -using System.Threading; -using System.Windows.Forms; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Windows.Forms; using System.Runtime.InteropServices; using CUEControls; using CUETools.AccurateRip; using CUETools.CTDB; -using CUETools.Processor; -using CUETools.Processor.Settings; -using CUETools.Ripper; -using Freedb; -using CUETools.Codecs; +using CUETools.Processor; +using CUETools.Processor.Settings; +using CUETools.Ripper; +#if DORMANT_FREEDB +using Freedb; +#endif +using CUETools.Codecs; using System.Xml; using System.Xml.Serialization; @@ -38,11 +41,13 @@ public partial class frmCUERipper : Form public readonly static XmlSerializerNamespaces xmlEmptyNamespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] { XmlQualifiedName.Empty }); public readonly static XmlWriterSettings xmlEmptySettings = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }; - public frmCUERipper() - { - InitializeComponent(); - _config = new CUEConfig(); - _startStop = new StartStop(); + public frmCUERipper() + { + InitializeComponent(); + if (!imageListMetadataSource.Images.ContainsKey(AccurateRipMeta.SourceKey)) + imageListMetadataSource.Images.Add(AccurateRipMeta.SourceKey, Properties.Resources.accuraterip16); + _config = new CUEConfig(); + _startStop = new StartStop(); cueRipperConfig = new CUERipperConfig(); m_icon_mgr = new CUEControls.ShellIconMgr(); m_icon_mgr.SetExtensionIcon(".flac", Properties.Resources.flac); @@ -452,20 +457,21 @@ private void SetupControls() buttonTracks.Enabled = data.selectedRelease != null && !running; buttonMetadata.Enabled = data.selectedRelease != null && !running; - buttonFreedbSubmit.Enabled = data.selectedRelease != null && !running; - buttonVA.Enabled = data.selectedRelease != null && !running && - data.selectedRelease.ImageKey == "freedb" && !data.selectedRelease.metadata.IsVarious() && (new CUEMetadata(data.selectedRelease.metadata)).FreedbToVarious(); - buttonEncoding.Enabled = data.selectedRelease != null && !running && - data.selectedRelease.ImageKey == "freedb" && (new CUEMetadata(data.selectedRelease.metadata)).FreedbToEncoding(); - buttonReload.Enabled = data.selectedRelease != null && !running; + buttonFreedbSubmit.Visible = false; + buttonFreedbSubmit.Enabled = false; + buttonVA.Visible = false; + buttonVA.Enabled = false; + buttonEncoding.Visible = false; + buttonEncoding.Enabled = false; + buttonReload.Enabled = data.selectedRelease != null && !running; buttonEjectDisk.Enabled = selectedDriveInfo !=null && !running; buttonSettings.Enabled = !running; } - private void CheckStop() - { - lock (_startStop) - { + private void CheckStop() + { + lock (_startStop) + { if (_startStop._stop) { _startStop._stop = false; @@ -480,11 +486,24 @@ private void CheckStop() } if (backgroundWorkerArtwork.IsBusy && backgroundWorkerArtwork.CancellationPending) { - throw new StopException(); - } - } - - private void UploadProgress(object sender, Krystalware.UploadHelper.UploadProgressEventArgs e) + throw new StopException(); + } + } + + private void CheckStopRequested() + { + lock (_startStop) + { + if (_startStop._stop) + { + _startStop._stop = false; + _startStop._pause = false; + throw new StopException(); + } + } + } + + private void UploadProgress(object sender, Krystalware.UploadHelper.UploadProgressEventArgs e) { CheckStop(); this.BeginInvoke((MethodInvoker)delegate() @@ -801,20 +820,34 @@ private void UpdateRelease() // }); //} - private void FreeDB_LookupProgress(object sender) - { - CheckStop(); - //_progress.percentDisk = (1.0 + _progress.percentDisk) / 2; + private void AccurateRipMetaLookupProgress(string detail) + { + CheckStop(); + string text = Properties.Resources.LookingUpVia + " " + AccurateRipMeta.DisplayName + "..." + (string.IsNullOrEmpty(detail) ? "" : " " + detail); + this.BeginInvoke((MethodInvoker)delegate() + { + toolStripStatusLabel1.Text = text; + toolStripProgressBar1.Value = (100 + 2 * toolStripProgressBar1.Value) / 3; + }); + } + +#if DORMANT_FREEDB + // FreeDB progress UI is dormant with the FreeDB provider. + private void FreeDB_LookupProgress(object sender) + { + CheckStop(); + //_progress.percentDisk = (1.0 + _progress.percentDisk) / 2; //_progress.input = e.Uri.ToString(); string text = Properties.Resources.LookingUpVia + " FreeDB..." + (sender is string ? " " + (sender as string) : ""); this.BeginInvoke((MethodInvoker)delegate() { toolStripStatusLabel1.Text = text; - toolStripProgressBar1.Value = (100 + 2 * toolStripProgressBar1.Value) / 3; - }); - } - - private CUEMetadataEntry CreateCUESheet(ICDRipper audioSource, CTDBResponseMeta release) + toolStripProgressBar1.Value = (100 + 2 * toolStripProgressBar1.Value) / 3; + }); + } +#endif + + private CUEMetadataEntry CreateCUESheet(ICDRipper audioSource, CTDBResponseMeta release) { CUEMetadataEntry entry = new CUEMetadataEntry(audioSource.TOC, release.source); entry.metadata.FillFromCtdb(release, entry.TOC.FirstAudio - 1); @@ -828,14 +861,17 @@ private CUEMetadataEntry CreateCUESheet(ICDRipper audioSource, CTDBResponseMeta // return entry; //} - private CUEMetadataEntry CreateCUESheet(ICDRipper audioSource, CDEntry cdEntry) - { - CUEMetadataEntry entry = new CUEMetadataEntry(audioSource.TOC, "freedb"); - entry.metadata.FillFromFreedb(cdEntry, entry.TOC.FirstAudio - 1); - return entry; - } - - private CUEMetadataEntry CreateCUESheet(ICDRipper audioSource) +#if DORMANT_FREEDB + // FreeDB CDEntry conversion is dormant with the FreeDB provider. + private CUEMetadataEntry CreateCUESheet(ICDRipper audioSource, CDEntry cdEntry) + { + CUEMetadataEntry entry = new CUEMetadataEntry(audioSource.TOC, "freedb"); + entry.metadata.FillFromFreedb(cdEntry, entry.TOC.FirstAudio - 1); + return entry; + } +#endif + + private CUEMetadataEntry CreateCUESheet(ICDRipper audioSource) { CUEMetadataEntry entry = new CUEMetadataEntry(audioSource.TOC, "blank"); entry.metadata.Artist = "Unknown Artist"; @@ -862,8 +898,12 @@ private void Lookup(object o) cueSheet.OpenCD(audioSource); cueSheet.Action = CUEAction.Encode; - this.BeginInvoke((MethodInvoker)delegate() { toolStripStatusLabel1.Text = Properties.Resources.LookingUpVia + " CTDB..."; }); - cueSheet.UseCUEToolsDB("CUERipper " + CUESheet.CUEToolsVersion, selectedDriveInfo.drive.ARName, false, loadAllMetadata ? CTDBMetadataSearch.Extensive : _config.advanced.metadataSearch); + CTDBMetadataSearch metadataSearch = loadAllMetadata ? CTDBMetadataSearch.Extensive : _config.advanced.metadataSearch; + bool loadAllMetadataRequested = loadAllMetadata; + loadAllMetadata = false; + + this.BeginInvoke((MethodInvoker)delegate() { toolStripStatusLabel1.Text = Properties.Resources.LookingUpVia + " CTDB..."; }); + cueSheet.UseCUEToolsDB("CUERipper " + CUESheet.CUEToolsVersion, selectedDriveInfo.drive.ARName, false, metadataSearch); cueSheet.CTDB.UploadHelper.onProgress += new EventHandler(UploadProgress); this.BeginInvoke((MethodInvoker)delegate() { toolStripStatusLabel1.Text = Properties.Resources.LookingUpVia + " AccurateRip..."; }); cueSheet.UseAccurateRip(); @@ -886,11 +926,10 @@ private void Lookup(object o) data.Releases.Add(CreateCUESheet(audioSource, ctdbMeta)); } - if (data.Releases.Count == 0 || loadAllMetadata) - { - loadAllMetadata = false; - - //this.BeginInvoke((MethodInvoker)delegate() { toolStripStatusLabel1.Text = Properties.Resources.LookingUpVia + " MusicBrainz..."; }); + bool noMetadataReleases = data.Releases.Count == 0; + if (noMetadataReleases || loadAllMetadataRequested) + { + //this.BeginInvoke((MethodInvoker)delegate() { toolStripStatusLabel1.Text = Properties.Resources.LookingUpVia + " MusicBrainz..."; }); //ReleaseQueryParameters p = new ReleaseQueryParameters(); //p.DiscId = audioSource.TOC.MusicBrainzId; @@ -917,83 +956,98 @@ private void Lookup(object o) //MusicBrainzService.Proxy = null; //MusicBrainzService.XmlRequest -= new EventHandler(MusicBrainz_LookupProgress); - this.BeginInvoke((MethodInvoker)delegate() { toolStripStatusLabel1.Text = Properties.Resources.LookingUpVia + " Freedb..."; }); - - FreedbHelper m_freedb = new FreedbHelper(); - m_freedb.Proxy = _config.GetProxy(); - m_freedb.UserName = _config.advanced.FreedbUser; - m_freedb.Hostname = _config.advanced.FreedbDomain; - m_freedb.ClientName = "CUERipper"; - m_freedb.Version = CUESheet.CUEToolsVersion; - try - { - m_freedb.SetDefaultSiteAddress(_config.advanced.FreedbSiteAddress); - } - catch (Exception ex) - { - this.Invoke((MethodInvoker)delegate() - { - MessageBox.Show(this, ex.Message, "FreedbHelper.SetDefaultSiteAddress", MessageBoxButtons.OK, MessageBoxIcon.Error); - }); - } - - QueryResult queryResult; - QueryResultCollection coll; - string code = string.Empty; - try - { - FreeDB_LookupProgress(this); - code = m_freedb.Query(AccurateRipVerify.CalculateCDDBQuery(audioSource.TOC), out queryResult, out coll); - if (code == FreedbHelper.ResponseCodes.CODE_200) - { - bool duplicate = false; - foreach (var ctdbMeta in cueSheet.CTDB.Metadata) - if (ctdbMeta.source == "freedb" && ctdbMeta.id == queryResult.Category + "/" + queryResult.Discid) - duplicate = true; - if (!duplicate) - { - FreeDB_LookupProgress(queryResult.Category + "/" + queryResult.Discid); - CDEntry cdEntry; - code = m_freedb.Read(queryResult, out cdEntry); - if (code == FreedbHelper.ResponseCodes.CODE_210) - { - CUEMetadataEntry r = CreateCUESheet(audioSource, cdEntry); - data.Releases.Add(r); - } - } - } - else - if (code == FreedbHelper.ResponseCodes.CODE_210 || - code == FreedbHelper.ResponseCodes.CODE_211) - { - foreach (QueryResult qr in coll) - { - bool duplicate = false; - foreach (var ctdbMeta in cueSheet.CTDB.Metadata) - if (ctdbMeta.source == "freedb" && ctdbMeta.id == qr.Category + "/" + qr.Discid) - duplicate = true; - if (!duplicate) - { - CDEntry cdEntry; - FreeDB_LookupProgress(qr.Category + "/" + qr.Discid); - code = m_freedb.Read(qr, out cdEntry); - if (code == FreedbHelper.ResponseCodes.CODE_210) - { - CUEMetadataEntry r = CreateCUESheet(audioSource, cdEntry); - data.Releases.Add(r); - } - } - } - } - } - catch (Exception ex) - { - System.Diagnostics.Trace.WriteLine(ex.Message); - } - } - - // Add a blank Release to the metadata selection drop-down list in any case. It can be used, if the metadata retrieved is not correct or undesired. - data.Releases.Add(CreateCUESheet(audioSource)); +#if DORMANT_FREEDB + // FreeDB lookup is dormant. AccurateRip Meta is the active replacement provider. + this.BeginInvoke((MethodInvoker)delegate() { toolStripStatusLabel1.Text = Properties.Resources.LookingUpVia + " Freedb..."; }); + + FreedbHelper m_freedb = new FreedbHelper(); + m_freedb.Proxy = _config.GetProxy(); + m_freedb.UserName = _config.advanced.FreedbUser; + m_freedb.Hostname = _config.advanced.FreedbDomain; + m_freedb.ClientName = "CUERipper"; + m_freedb.Version = CUESheet.CUEToolsVersion; + try + { + m_freedb.SetDefaultSiteAddress(_config.advanced.FreedbSiteAddress); + } + catch (Exception ex) + { + this.Invoke((MethodInvoker)delegate() + { + MessageBox.Show(this, ex.Message, "FreedbHelper.SetDefaultSiteAddress", MessageBoxButtons.OK, MessageBoxIcon.Error); + }); + } + + QueryResult queryResult; + QueryResultCollection coll; + string code = string.Empty; + try + { + FreeDB_LookupProgress(this); + code = m_freedb.Query(AccurateRipVerify.CalculateCDDBQuery(audioSource.TOC), out queryResult, out coll); + if (code == FreedbHelper.ResponseCodes.CODE_200) + { + bool duplicate = false; + foreach (var ctdbMeta in cueSheet.CTDB.Metadata) + if (ctdbMeta.source == "freedb" && ctdbMeta.id == queryResult.Category + "/" + queryResult.Discid) + duplicate = true; + if (!duplicate) + { + FreeDB_LookupProgress(queryResult.Category + "/" + queryResult.Discid); + CDEntry cdEntry; + code = m_freedb.Read(queryResult, out cdEntry); + if (code == FreedbHelper.ResponseCodes.CODE_210) + { + CUEMetadataEntry r = CreateCUESheet(audioSource, cdEntry); + data.Releases.Add(r); + } + } + } + else + if (code == FreedbHelper.ResponseCodes.CODE_210 || + code == FreedbHelper.ResponseCodes.CODE_211) + { + foreach (QueryResult qr in coll) + { + bool duplicate = false; + foreach (var ctdbMeta in cueSheet.CTDB.Metadata) + if (ctdbMeta.source == "freedb" && ctdbMeta.id == qr.Category + "/" + qr.Discid) + duplicate = true; + if (!duplicate) + { + CDEntry cdEntry; + FreeDB_LookupProgress(qr.Category + "/" + qr.Discid); + code = m_freedb.Read(qr, out cdEntry); + if (code == FreedbHelper.ResponseCodes.CODE_210) + { + CUEMetadataEntry r = CreateCUESheet(audioSource, cdEntry); + data.Releases.Add(r); + } + } + } + } + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine(ex.Message); + } +#endif + } + + // AccurateRip Meta is a peer of the CTDB metadata sources rather than a fallback for + // them: whenever CTDB is asked for metadata, this provider is asked too, so its release + // and its cover art appear on the first lookup instead of only after Reload. + if (metadataSearch != CTDBMetadataSearch.None) + { + AccurateRipMetaLookupProgress(null); + var provider = new AccurateRipMetaProvider(); + CUEMetadataEntry accurateRipMeta = provider.Lookup(audioSource.TOC, _config.GetProxy(), CheckStopRequested); + if (accurateRipMeta != null) + data.Releases.Add(accurateRipMeta); + } + + // Add a blank Release to the metadata selection drop-down list in any case. It can be used, if the metadata retrieved is not correct or undesired. + data.Releases.Add(CreateCUESheet(audioSource)); _workThread = null; if (musicbrainzError != "") @@ -1019,7 +1073,12 @@ private void Lookup(object o) toolStripStatusLabelMusicBrainz.Text = mbresults_count > 0 ? mbresults_count.ToString() : ""; toolStripStatusLabelMusicBrainz.ToolTipText = "Musicbrainz: " + (mbresults_count > 0 ? mbresults_count.ToString() + " entries found." : (musicbrainzError + "click to submit.")); if (_config.advanced.coversSearch != CUEConfigAdvanced.CTDBCoversSearch.None) - backgroundWorkerArtwork.RunWorkerAsync(new BackgroundWorkerArtworkArgs() { cueSheet = cueSheet, meta = data.selectedRelease }); + backgroundWorkerArtwork.RunWorkerAsync(new BackgroundWorkerArtworkArgs() + { + cueSheet = cueSheet, + meta = data.selectedRelease, + releases = data.Releases.ToList() + }); }); } @@ -1514,24 +1573,15 @@ private void buttonReload_Click(object sender, EventArgs e) _workThread.Start(selectedDriveInfo.drive); } - private void buttonVA_Click(object sender, EventArgs e) - { - if (data.selectedRelease == null) return; - data.selectedRelease.metadata.FreedbToVarious(); - UpdateRelease(); - data.Releases.ResetItem(bnComboBoxRelease.SelectedIndex); - SetupControls(); - } - - private void buttonEncoding_Click(object sender, EventArgs e) - { - if (data.selectedRelease == null) return; - data.selectedRelease.metadata.FreedbToEncoding(); - UpdateRelease(); - data.Releases.ResetItem(bnComboBoxRelease.SelectedIndex); - UpdateOutputPath(); - SetupControls(); - } + private void buttonVA_Click(object sender, EventArgs e) + { + // FreeDB various-artist repair is dormant. AccurateRip Meta does not need this action. + } + + private void buttonEncoding_Click(object sender, EventArgs e) + { + // FreeDB encoding repair is dormant. AccurateRip Meta responses are Unicode JSON. + } private void listTracks_Click(object sender, EventArgs e) { @@ -1558,9 +1608,10 @@ private void listTracks_Click(object sender, EventArgs e) } } - private void FreedbSubmit(object o) - { - StringCollection tmp = new StringCollection(); +#if DORMANT_FREEDB + private void FreedbSubmit(object o) + { + StringCollection tmp = new StringCollection(); tmp.Add("DTITLE="); CDEntry entry = new CDEntry(tmp); entry.Artist = data.selectedRelease.metadata.Artist; @@ -1650,17 +1701,14 @@ private void FreedbSubmit(object o) }); } _workThread = null; - this.BeginInvoke((MethodInvoker)delegate() { SetupControls(); }); - } - - private void buttonFreedbSubmit_Click(object sender, EventArgs e) - { - _workThread = new Thread(FreedbSubmit); - _workThread.Priority = ThreadPriority.BelowNormal; - _workThread.IsBackground = true; - SetupControls(); - _workThread.Start(); - } + this.BeginInvoke((MethodInvoker)delegate() { SetupControls(); }); + } +#endif + + private void buttonFreedbSubmit_Click(object sender, EventArgs e) + { + // FreeDB submission is dormant. AccurateRip Meta currently has no submission API. + } List albumArt = new List(); int currentAlbumArt = 0;//, frontAlbumArt = -1; @@ -1701,22 +1749,73 @@ private void SelectAlbumArt() resetPictureBox(null); } - private void backgroundWorkerArtwork_DoWork(object sender, DoWorkEventArgs e) - { - var args = e.Argument as BackgroundWorkerArtworkArgs; - var cueSheet = args.cueSheet; - albumArt.Clear(); - currentAlbumArt = 0; - //frontAlbumArt = -1; - var knownUrls = new List(); - var firstUrls = new List(); - - if (args.meta != null && args.meta.metadata.AlbumArt.Count > 0) - foreach (var aa in args.meta.metadata.AlbumArt) - firstUrls.Add(aa.uri); - - for (int i = 0; i < 2; i++) - { + private void backgroundWorkerArtwork_DoWork(object sender, DoWorkEventArgs e) + { + var args = e.Argument as BackgroundWorkerArtworkArgs; + var cueSheet = args.cueSheet; + albumArt.Clear(); + currentAlbumArt = 0; + var knownUrls = new List(); + var firstUrls = new List(); + var selectedReleaseCovers = new List(); + var otherReleaseCovers = new List(); + + var releases = args.releases ?? new List(); + if (releases.Count == 0 && args.meta != null) + releases.Add(args.meta); + + foreach (var release in releases) + { + if (release == null || release.metadata == null || release.metadata.AlbumArt == null) + continue; + + foreach (var releaseCover in release.metadata.AlbumArt) + { + if (releaseCover == null) + continue; + if (_config.advanced.coversSearch == CUEConfigAdvanced.CTDBCoversSearch.Primary && !releaseCover.primary) + continue; + + // args.meta is the same CUEMetadataEntry instance captured from data.Releases for selected-release priority. + if (object.ReferenceEquals(release, args.meta)) + { + if (!string.IsNullOrEmpty(releaseCover.uri)) + firstUrls.Add(releaseCover.uri); + selectedReleaseCovers.Add(releaseCover); + } + else + { + otherReleaseCovers.Add(releaseCover); + } + } + } + + var releaseCovers = selectedReleaseCovers.Concat(otherReleaseCovers); + foreach (var releaseCover in releaseCovers) + { + string fetchUrl = !string.IsNullOrEmpty(releaseCover.uri150) ? releaseCover.uri150 : releaseCover.uri; + if (string.IsNullOrEmpty(fetchUrl) || knownUrls.Contains(fetchUrl)) + continue; + + var ms = new MemoryStream(); + if (!cueSheet.CTDB.FetchFile(fetchUrl, ms)) + continue; + + lock (this.albumArt) + { + if (backgroundWorkerArtwork.CancellationPending) + { + e.Cancel = true; + return; + } + this.albumArt.Add(new AlbumArt(releaseCover, ms.ToArray())); + } + knownUrls.Add(fetchUrl); + backgroundWorkerArtwork.ReportProgress(0); + } + + for (int i = 0; i < 2; i++) + { foreach (var metadata in cueSheet.CTDB.Metadata) { if (metadata.coverart == null) @@ -1914,10 +2013,11 @@ private void listMetadata_PreviewKeyDown(object sender, PreviewKeyDownEventArgs } internal class BackgroundWorkerArtworkArgs - { - public CUESheet cueSheet; - public CUEMetadataEntry meta; - } + { + public CUESheet cueSheet; + public CUEMetadataEntry meta; + public List releases; + } internal class AlbumArt { diff --git a/CUETools.ALACEnc/CUETools.ALACEnc.csproj b/CUETools.ALACEnc/CUETools.ALACEnc.csproj index e6288638d..b5645b86a 100644 --- a/CUETools.ALACEnc/CUETools.ALACEnc.csproj +++ b/CUETools.ALACEnc/CUETools.ALACEnc.csproj @@ -1,9 +1,7 @@  - net47;net20;netcoreapp2.0 - false - 2.0.9 + net10.0 2.2.6.0 CUETools.ALACEnc CUETools.ALACEnc diff --git a/CUETools.ARCUE/CUETools.ARCUE.csproj b/CUETools.ARCUE/CUETools.ARCUE.csproj index 1075c0667..e96771d2f 100644 --- a/CUETools.ARCUE/CUETools.ARCUE.csproj +++ b/CUETools.ARCUE/CUETools.ARCUE.csproj @@ -1,9 +1,7 @@  - net47;netcoreapp2.0 - false - 2.0.9 + net10.0 2.2.6.0 CUETools.ARCUE CUETools.ARCUE diff --git a/CUETools.AccurateRip/CUETools.AccurateRip.csproj b/CUETools.AccurateRip/CUETools.AccurateRip.csproj index fc2763575..f68e72078 100644 --- a/CUETools.AccurateRip/CUETools.AccurateRip.csproj +++ b/CUETools.AccurateRip/CUETools.AccurateRip.csproj @@ -1,7 +1,7 @@  - net20;net47;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.AccurateRip CUETools.AccurateRip diff --git a/CUETools.CDImage/CUETools.CDImage.csproj b/CUETools.CDImage/CUETools.CDImage.csproj index c103a5dca..1e49300c7 100644 --- a/CUETools.CDImage/CUETools.CDImage.csproj +++ b/CUETools.CDImage/CUETools.CDImage.csproj @@ -1,7 +1,7 @@  - net20;net47;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.CDImage CUETools.CDImage diff --git a/CUETools.CLParity/CLParity.cs b/CUETools.CLParity/CLParity.cs index 6a56349f9..218708cf4 100644 --- a/CUETools.CLParity/CLParity.cs +++ b/CUETools.CLParity/CLParity.cs @@ -111,7 +111,7 @@ public class CLParityWriter : IAudioDest bool inited = false; OpenCLManager OCLMan; - Program openCLProgram; + CLProgram openCLProgram; CLParityTask task1; CLParityTask task2; @@ -123,6 +123,7 @@ public class CLParityWriter : IAudioDest public CLParityWriter(string path, Stream IO, AudioPCMConfig pcm) { _pcm = pcm; + _encoderSettings = new CUETools.Codecs.WAV.EncoderSettings(pcm); if (pcm.BitsPerSample != 16) throw new Exception("Bits per sample must be 16."); if (pcm.ChannelCount != 2) @@ -137,6 +138,7 @@ public CLParityWriter(string path, AudioPCMConfig pcm) } internal CLParitySettings _settings = new CLParitySettings(); + private readonly IAudioEncoderSettings _encoderSettings; public object Settings { @@ -152,6 +154,8 @@ public object Settings } } + IAudioEncoderSettings IAudioDest.Settings => _encoderSettings; + //[DllImport("kernel32.dll")] //static extern bool GetThreadTimes(IntPtr hThread, out long lpCreationTime, out long lpExitTime, out long lpKernelTime, out long lpUserTime); //[DllImport("kernel32.dll")] @@ -388,7 +392,7 @@ public unsafe void do_output_frames(int strideCount) internal class CLParityTask { - Program openCLProgram; + CLProgram openCLProgram; public CommandQueue openCLCQ; public Kernel reedSolomonInit; public Kernel reedSolomonInitGx; @@ -432,7 +436,7 @@ internal class CLParityTask public CLParityWriter writer; public bool UseMappedMemory = false; - unsafe public CLParityTask(Program _openCLProgram, CLParityWriter writer, int groupSize, int npar, int stride, int maxStridesCount) + unsafe public CLParityTask(CLProgram _openCLProgram, CLParityWriter writer, int groupSize, int npar, int stride, int maxStridesCount) { this.UseMappedMemory = writer._settings.MappedMemory || writer._settings.DeviceType == OpenCLDeviceType.CPU; this.groupSize = groupSize; @@ -629,7 +633,6 @@ internal unsafe void EnqueueKernels() } } -#if LKJLKJLJK public static class OpenCLExtensions { public static void SetArgs(this Kernel kernel, params object[] args) @@ -660,5 +663,4 @@ public static void EnqueueNDRangeKernel(this CommandQueue queue, Kernel kernel, queue.EnqueueNDRangeKernel(kernel, 2, null, new long[] { localSizeX * globalSizeX, localSizeY * globalSizeY }, new long[] { localSizeX, localSizeY }); } } -#endif } diff --git a/CUETools.CLParity/CUETools.CLParity.csproj b/CUETools.CLParity/CUETools.CLParity.csproj index 7c131fcc9..363bc7022 100644 --- a/CUETools.CLParity/CUETools.CLParity.csproj +++ b/CUETools.CLParity/CUETools.CLParity.csproj @@ -1,128 +1,37 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {D41FEFF9-9D43-4049-BF9B-B13D9AC7E192} + net10.0-windows + 2.2.6.0 Library - Properties - CUETools.CLParity CUETools.CLParity - - - 3.5 - - - v4.7 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - true - full - false - ..\bin\Debug\plugins\net47\ - DEBUG;TRACE - prompt - 4 - true - AllRules.ruleset - - - pdbonly - true - ..\bin\Release\plugins\net47\ - TRACE - prompt - 4 + CUETools.CLParity + 7.0 + true true - AllRules.ruleset + false + $(NoWarn);WFO1000 + ..\bin\$(Configuration)\$(TargetFramework)\plugins\ + false + $(BaseIntermediateOutputPath)\$(Configuration)\$(TargetFramework)\ - - - False - ..\ThirdParty\OpenCLNet.dll - - - - - - - - - - True - True - Resources.resx - - - - - {082D6B9E-326E-4D15-9798-EDAE9EDE70A6} - CUETools.Codecs.Flake - False - - - {6458A13A-30EF-45A9-9D58-E5031B17BEE2} - CUETools.Codecs + + + False - + + - - PreserveNewest - - - - ResXFileCodeGenerator - Resources.Designer.cs - - + + + + - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - + + PreserveNewest + - - - - - - - - - \ No newline at end of file + + diff --git a/CUETools.CLParity/Properties/Resources.Designer.cs b/CUETools.CLParity/Properties/Resources.Designer.cs index 39b66af0a..baf61d06c 100644 --- a/CUETools.CLParity/Properties/Resources.Designer.cs +++ b/CUETools.CLParity/Properties/Resources.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18033 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -19,7 +19,7 @@ namespace CUETools.CLParity.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/CUETools.CTDB.Converter/CUETools.CTDB.Converter.csproj b/CUETools.CTDB.Converter/CUETools.CTDB.Converter.csproj index cc91ce79d..b1f7938d5 100644 --- a/CUETools.CTDB.Converter/CUETools.CTDB.Converter.csproj +++ b/CUETools.CTDB.Converter/CUETools.CTDB.Converter.csproj @@ -1,9 +1,7 @@  - net47;net20;netcoreapp2.0 - false - 2.0.9 + net10.0 2.2.6.0 CUETools.CTDB.Converter CUETools.CTDB.Converter diff --git a/CUETools.CTDB.EACPlugin/CUETools.CTDB.EACPlugin.csproj b/CUETools.CTDB.EACPlugin/CUETools.CTDB.EACPlugin.csproj index 46037b6dd..91a6644a6 100644 --- a/CUETools.CTDB.EACPlugin/CUETools.CTDB.EACPlugin.csproj +++ b/CUETools.CTDB.EACPlugin/CUETools.CTDB.EACPlugin.csproj @@ -1,180 +1,32 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {816D964C-9772-46C5-AF1D-49E8C78A1E7C} - Library - Properties - CUETools.CTDB.EACPlugin - CUETools.CTDB.EACPlugin - v2.0 - 512 - - - 3.5 - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - true - full - false - ..\bin\Debug\interop\EAC\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - ..\bin\Release\interop\EAC\ - TRACE - prompt - 4 - AllRules.ruleset - - - - Form - - - FormSubmitParity.cs - - - Form - - - FormMetadata.cs - - - Component - - - ImagePreview.cs - - - - - - Form - - - Options.cs - - - - True - True - Resources.resx - - - - - FormSubmitParity.cs - - - FormMetadata.cs - - - ImagePreview.cs - - - Options.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - - - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - - - - .\Interop.HelperFunctionsLib.dll - True - - - - - - - - - {5802c7e9-157e-4124-946d-70b5ae48a5a1} - CUETools.AccurateRip - - - {1dd41038-d885-46c5-8dde-e0b82f066584} - CUETools.CDImage - - - {6458a13a-30ef-45a9-9d58-e5031b17bee2} - CUETools.Codecs - - - {c36efaa5-4f81-4a29-800d-ba866ff13bc8} - CUETools.CTDB.Types - - - {aa2a9a7e-45fb-4632-ad85-85b0e556f818} - CUETools.CTDB - - - {eceb839c-171b-4535-958f-9899310a0342} - CUETools.Parity - - - - - + + + + net10.0-windows + Library + CUETools.CTDB.EACPlugin + CUETools.CTDB.EACPlugin + true + false + $(NoWarn);WFO1000 + ..\bin\$(Configuration)\$(TargetFramework)\interop\EAC\ + false + $(BaseIntermediateOutputPath)\$(Configuration)\$(TargetFramework)\ + + + + + .\Interop.HelperFunctionsLib.dll + True + + + + + + + + + + + + + diff --git a/CUETools.CTDB.Types/CUETools.CTDB.Types.csproj b/CUETools.CTDB.Types/CUETools.CTDB.Types.csproj index 409d6327c..e7c1ed0ce 100644 --- a/CUETools.CTDB.Types/CUETools.CTDB.Types.csproj +++ b/CUETools.CTDB.Types/CUETools.CTDB.Types.csproj @@ -1,7 +1,7 @@  - net20;net47;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.CTDB.Types CUETools.CTDB diff --git a/CUETools.CTDB/CUETools.CTDB.csproj b/CUETools.CTDB/CUETools.CTDB.csproj index 59df590f6..62d6975bb 100644 --- a/CUETools.CTDB/CUETools.CTDB.csproj +++ b/CUETools.CTDB/CUETools.CTDB.csproj @@ -1,7 +1,7 @@  - net20;net47;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.CTDB CUETools.CTDB @@ -22,15 +22,9 @@ - - - - - - - - - + + + diff --git a/CUETools.CTDB/CUEToolsDB.cs b/CUETools.CTDB/CUEToolsDB.cs index c8b0dc3e1..077e80a0f 100644 --- a/CUETools.CTDB/CUEToolsDB.cs +++ b/CUETools.CTDB/CUEToolsDB.cs @@ -7,17 +7,13 @@ using System.Security.Cryptography; using System.Text; using System.Xml.Serialization; -using CUETools.AccurateRip; -using CUETools.CDImage; -using CUETools.Parity; -#if NET20 -using System.Management; -#else -using DeviceId; -using DeviceId.Encoders; -using DeviceId.Formatters; -#endif -using Krystalware.UploadHelper; +using CUETools.AccurateRip; +using CUETools.CDImage; +using CUETools.Parity; +using DeviceId; +using DeviceId.Encoders; +using DeviceId.Formatters; +using Krystalware.UploadHelper; namespace CUETools.CTDB { @@ -281,31 +277,16 @@ public bool FetchFile(string url, Stream output) public static string GetUUID() { - if (uuidInfo == null) - { -#if NET20 - string id = "CTDB userid"; - using (ManagementClass mc = new ManagementClass("Win32_ComputerSystemProduct")) - foreach (ManagementObject mo in mc.GetInstances()) - { - id = id + mo.Properties["UUID"].Value.ToString(); - break; - } - byte[] hashBytes = (new SHA1CryptoServiceProvider()).ComputeHash(Encoding.ASCII.GetBytes(id)); - uuidInfo = Convert.ToBase64String(hashBytes).Replace('+', '.').Replace('/', '_').Replace('=', '-'); -#else - uuidInfo = new DeviceIdBuilder() - .AddMachineName() -#if NET47 - .AddProcessorId() - .AddMotherboardSerialNumber() -#endif - .UseFormatter(new HashDeviceIdFormatter(() => SHA256.Create(), new Base64UrlByteArrayEncoder())) - .ToString(); -#endif - } - return uuidInfo; - } + if (uuidInfo == null) + { + uuidInfo = new DeviceIdBuilder() + .AddMachineName() + .AddMacAddress(excludeWireless: true, excludeDockerBridge: true) + .UseFormatter(new HashDeviceIdFormatter(() => SHA256.Create(), new Base64UrlByteArrayEncoder())) + .ToString(); + } + return uuidInfo; + } public CTDBResponse Submit(int confidence, int quality, string artist, string title, string barcode) { diff --git a/CUETools.ChaptersToCue/CUETools.ChaptersToCue.csproj b/CUETools.ChaptersToCue/CUETools.ChaptersToCue.csproj index 141d158f8..5c1f6f848 100644 --- a/CUETools.ChaptersToCue/CUETools.ChaptersToCue.csproj +++ b/CUETools.ChaptersToCue/CUETools.ChaptersToCue.csproj @@ -1,110 +1,20 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {6ADBBF4B-AD3A-4782-A694-18662196780B} + net10.0 Exe - Properties - CUETools.ChaptersToCue CUETools.ChaptersToCue - - - 3.5 - - - v4.7 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - true - full - false - ..\bin\Debug\net47\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - ..\bin\Release\net47\ - TRACE - prompt - 4 - AllRules.ruleset + CUETools.ChaptersToCue + false + ..\bin\$(Configuration)\ + - - - - - - - - - - - {1DD41038-D885-46C5-8DDE-E0B82F066584} - CUETools.CDImage - - - {c36efaa5-4f81-4a29-800d-ba866ff13bc8} - CUETools.CTDB.Types - - - {AA2A9A7E-45FB-4632-AD85-85B0E556F818} - CUETools.CTDB - - - {6458A13A-30EF-45A9-9D58-E5031B17BEE2} - CUETools.Codecs - - - {1219a514-d3fa-40db-bbb2-92ce05e35839} - TaglibSharp - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - + + + + + - - - \ No newline at end of file + + diff --git a/CUETools.Codecs.ALAC/CUETools.Codecs.ALAC.csproj b/CUETools.Codecs.ALAC/CUETools.Codecs.ALAC.csproj index effe2f82e..18600e1e6 100644 --- a/CUETools.Codecs.ALAC/CUETools.Codecs.ALAC.csproj +++ b/CUETools.Codecs.ALAC/CUETools.Codecs.ALAC.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Codecs.ALAC CUETools.Codecs diff --git a/CUETools.Codecs.CoreAudio/CUETools.Codecs.CoreAudio.csproj b/CUETools.Codecs.CoreAudio/CUETools.Codecs.CoreAudio.csproj index 94861db06..53edbdf33 100644 --- a/CUETools.Codecs.CoreAudio/CUETools.Codecs.CoreAudio.csproj +++ b/CUETools.Codecs.CoreAudio/CUETools.Codecs.CoreAudio.csproj @@ -1,7 +1,7 @@  - net47;net20 + netstandard2.1 2.2.6.0 CUETools.Codecs.CoreAudio CUETools.Codecs.CoreAudio diff --git a/CUETools.Codecs.DirectSound/CUETools.Codecs.DirectSound.csproj b/CUETools.Codecs.DirectSound/CUETools.Codecs.DirectSound.csproj index 84fd72b2d..d63d3447e 100644 --- a/CUETools.Codecs.DirectSound/CUETools.Codecs.DirectSound.csproj +++ b/CUETools.Codecs.DirectSound/CUETools.Codecs.DirectSound.csproj @@ -1,99 +1,29 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {5A9FB016-6388-475D-AB33-6F86AD49FDAD} + net10.0-windows + 1.0.0.0 Library - Properties - CUETools.Codecs.DirectSound CUETools.Codecs.DirectSound - v4.7 - 512 - - - - - 3.5 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - true - full - false - ..\bin\Debug\net47\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - ..\bin\Release\net47\ - TRACE - prompt - 4 - AllRules.ruleset + CUETools.Codecs.DirectSound + 7.0 + true + false + $(NoWarn);WFO1000 + ..\bin\$(Configuration)\$(TargetFramework)\plugins\ + false + $(BaseIntermediateOutputPath)\$(Configuration)\$(TargetFramework)\ - - - False - - - - - - - - - - - - - {6458A13A-30EF-45A9-9D58-E5031B17BEE2} - CUETools.Codecs + + + + False - + + - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - + + - - - \ No newline at end of file + + diff --git a/CUETools.Codecs.DirectSound/DirectSoundOut.cs b/CUETools.Codecs.DirectSound/DirectSoundOut.cs index 2ed8e3ca7..2ad9e50c2 100644 --- a/CUETools.Codecs.DirectSound/DirectSoundOut.cs +++ b/CUETools.Codecs.DirectSound/DirectSoundOut.cs @@ -1,297 +1,77 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.IO; -using System.Threading; +using System; using System.Windows.Forms; -using Microsoft.DirectX; -using Microsoft.DirectX.DirectSound; +using CUETools.Codecs.CoreAudio; +using NAudio.CoreAudioApi; namespace CUETools.Codecs.DirectSound { - public class DirectSoundOut : IWavePlayer - { - private Device dSound; - private WaveFormat format;// = new WaveFormat(); - private BufferDescription description = new BufferDescription(); - private SecondaryBuffer secondaryBuffer; - private Notify notify; - private MemoryStream pcmStream; - private int SecBufByteSize; - private AudioEncoderSettings m_settings; - PlaybackState playbackState = PlaybackState.Stopped; - - /// - /// Playback Stopped - /// - public event EventHandler PlaybackStopped; - - AutoResetEvent - SecBufNotifyAtBegin = new AutoResetEvent(false), - SecBufNotifyAtOneThird = new AutoResetEvent(false), - SecBufNotifyAtTwoThirds = new AutoResetEvent(false); - WaitHandle[] SecBufWaitHandles; - - public DirectSoundOut(Control owner, AudioPCMConfig pcm, int delay) - { - this.m_settings = new AudioEncoderSettings(pcm); - - //buffer = new CyclicBuffer(44100*4/10); - //output = new CycilcBufferOutputStream(buffer); - //input = new CycilcBufferInputStream(buffer); - - dSound = new Device(); - dSound.SetCooperativeLevel(owner, CooperativeLevel.Priority); - format.AverageBytesPerSecond = pcm.SampleRate * pcm.BlockAlign; - format.BitsPerSample = (short)pcm.BitsPerSample; - format.BlockAlign = (short)pcm.BlockAlign; - format.Channels = (short)pcm.ChannelCount; - format.SamplesPerSecond = pcm.SampleRate; - format.FormatTag = WaveFormatTag.Pcm; - SecBufByteSize = delay * pcm.SampleRate * pcm.BlockAlign / 1000; - description.Format = format; - description.BufferBytes = SecBufByteSize; - description.CanGetCurrentPosition = true; - description.ControlPositionNotify = true; - //description.ControlVolume = true; - description.GlobalFocus = true; - secondaryBuffer = new SecondaryBuffer(description, dSound); - //secondaryBuffer.Volume = 100; - - notify = new Notify(secondaryBuffer); - BufferPositionNotify[] bufferPositions = new BufferPositionNotify[3]; - bufferPositions[0].Offset = 0; - bufferPositions[0].EventNotifyHandle = SecBufNotifyAtBegin.Handle; - bufferPositions[1].Offset = SecBufByteSize / 3; - bufferPositions[1].EventNotifyHandle = SecBufNotifyAtOneThird.Handle; - bufferPositions[2].Offset = 2 * SecBufByteSize / 3; - bufferPositions[2].EventNotifyHandle = SecBufNotifyAtTwoThirds.Handle; - notify.SetNotificationPositions(bufferPositions); - pcmStream = new MemoryStream(SecBufByteSize); - - SecBufWaitHandles = new WaitHandle[] { SecBufNotifyAtBegin, SecBufNotifyAtOneThird, SecBufNotifyAtTwoThirds }; - - //wavoutput = new WAVWriter("", output, pcm); - } - - /// - /// Volume - /// - public float Volume - { - get - { - return 1.0f; - } - set - { - if (value != 1.0f) - { - throw new NotImplementedException(); - } - } - } - - //int SecBufNextWritePosition = 0; - //bool SecBufInitialLoad = false; - - bool playing = false; - public void Write(AudioBuffer src) - { - //wavoutput.Write(src); - - pcmStream.SetLength(0); - pcmStream.Write(src.Bytes, 0, src.ByteLength); - pcmStream.Position = 0; - //pcmStream.Position = 0; - - //while (true) - //{ - // if (SecBufInitialLoad) - // { - // int count = Math.Min(src.ByteLength, SecBufByteSize - SecBufNextWritePosition); - // if (count > 0) - // { - // secondaryBuffer.Write(SecBufNextWritePosition, pcmStream, count, LockFlag.None); - // SecBufNextWritePosition += count; - // pcmStream.Position += count; - // } - - // if (SecBufByteSize == SecBufNextWritePosition) - // { - // // Finished filling the buffer - // SecBufInitialLoad = false; - // SecBufNextWritePosition = 0; - - // // So start the playback in its own thread - // secondaryBuffer.Play(0, BufferPlayFlags.Looping); - - // // Yield rest of timeslice so playback can - // // start right away. - // Thread.Sleep(0); - // } - // else - // { - // continue; // Get more PCM data - // } - // } - - // Exhaust the current PCM data by writing the data into secondaryBuffer - while (pcmStream.Position < pcmStream.Length) - { - int PlayPosition, WritePosition; - - secondaryBuffer.GetCurrentPosition(out PlayPosition, out WritePosition); - - int WriteCount = (int)Math.Min( - (SecBufByteSize + PlayPosition - WritePosition) % SecBufByteSize, - pcmStream.Length - pcmStream.Position); - - if (WriteCount > 0) - { - secondaryBuffer.Write( - WritePosition, - pcmStream, - WriteCount, - LockFlag.None); - pcmStream.Position += WriteCount; - if (!playing) - { - secondaryBuffer.Play(0, 0); - playing = true; - } - } - else - { - WaitHandle.WaitAny(SecBufWaitHandles, new TimeSpan(0, 0, 5), true); - } - } - } - - /// - /// Begin Playback - /// - public void Play() - { - switch (playbackState) - { - case PlaybackState.Playing: - return; - case PlaybackState.Paused: - playbackState = PlaybackState.Playing; - return; - case PlaybackState.Stopped: - playbackState = PlaybackState.Playing; - //playThread = new Thread(new ThreadStart(PlayThread)); - //playThread.Priority = ThreadPriority.Highest; - //playThread.IsBackground = true; - //playThread.Name = "Pro Audio"; - //playThread.Start(); - return; - } - } - - /// - /// Stop playback and flush buffers - /// - public void Stop() - { - if (playbackState != PlaybackState.Stopped) - { - playbackState = PlaybackState.Stopped; - //if (frameEventWaitHandle != null) - // frameEventWaitHandle.Set(); - //playThread.Join(); - //playThread = null; - //ReleaseBuffer(readBuffers[0], false, 0); - //ReleaseBuffer(readBuffers[1], false, 0); - //active = null; - //active_offset = 0; - } - } - - /// - /// Stop playback without flushing buffers - /// - public void Pause() - { - if (playbackState == PlaybackState.Playing) - { - playbackState = PlaybackState.Paused; - } - //if (frameEventWaitHandle != null) - // frameEventWaitHandle.Set(); - } - - /// - /// Playback State - /// - public PlaybackState PlaybackState - { - get { return playbackState; } - } - - public void Close() - { - if (secondaryBuffer != null) - { - secondaryBuffer.Dispose(); - secondaryBuffer = null; - } - if (dSound != null) - { - dSound.Dispose(); - dSound = null; - } - //wavoutput.Close(); - } - - public void Delete() - { - Close(); - } - - #region IAudioDest Members - - public long Position - { - get - { - return 0; - } - } - - public long FinalSampleCount - { - set { ; } - } - - public object Settings - { - get - { - return m_settings; - } - } - - public string Path { get { return null; } } - - #endregion - - #region IDisposable Members - - /// - /// Dispose - /// - public void Dispose() - { - //if (audioClient != null) - { - Stop(); - } - - } - - #endregion - } + public class DirectSoundOut : CUETools.Codecs.IWavePlayer + { + private readonly WasapiOut player; + + public DirectSoundOut(Control owner, AudioPCMConfig pcm, int delay) + { + player = new WasapiOut(WasapiOut.GetDefaultAudioEndpoint(), AudioClientShareMode.Shared, true, delay, pcm); + } + + public event EventHandler PlaybackStopped + { + add { player.PlaybackStopped += value; } + remove { player.PlaybackStopped -= value; } + } + + public float Volume + { + get { return player.Volume; } + set { player.Volume = value; } + } + + public CUETools.Codecs.PlaybackState PlaybackState => (CUETools.Codecs.PlaybackState)player.PlaybackState; + + public long Position => player.Position; + + public long FinalSampleCount + { + set { player.FinalSampleCount = value; } + } + + public IAudioEncoderSettings Settings => player.Settings; + + public string Path => player.Path; + + public void Write(AudioBuffer src) + { + player.Write(src); + } + + public void Play() + { + player.Play(); + } + + public void Stop() + { + player.Stop(); + } + + public void Pause() + { + player.Pause(); + } + + public void Close() + { + player.Close(); + } + + public void Delete() + { + player.Delete(); + } + + public void Dispose() + { + player.Dispose(); + } + } } diff --git a/CUETools.Codecs.FLACCL/CUETools.Codecs.FLACCL.csproj b/CUETools.Codecs.FLACCL/CUETools.Codecs.FLACCL.csproj index eef5c9144..fb605770a 100644 --- a/CUETools.Codecs.FLACCL/CUETools.Codecs.FLACCL.csproj +++ b/CUETools.Codecs.FLACCL/CUETools.Codecs.FLACCL.csproj @@ -1,131 +1,41 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {DFE55765-564C-4B8F-993B-A94C4D1C212E} - Library - Properties - CUETools.Codecs.FLACCL - CUETools.Codecs.FLACCL - - - 3.5 - - - v4.7 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - true - full - false - ..\bin\Debug\net47\plugins\ - DEBUG;TRACE - prompt - 4 - true - AllRules.ruleset - - - pdbonly - true - ..\bin\Release\net47\plugins\ - TRACE - prompt - 4 - true - AllRules.ruleset - - - - - - - - - - - True - True - Resources.resx - - - - - {082D6B9E-326E-4D15-9798-EDAE9EDE70A6} - CUETools.Codecs.Flake - False - - - {6458A13A-30EF-45A9-9D58-E5031B17BEE2} - CUETools.Codecs - False - - - {8373e3b2-16f0-4fd3-a13b-0b0248ffc228} - OpenCLNet - - - - - PreserveNewest - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - - - - - - - - - - \ No newline at end of file + + + + net10.0-windows + 2.2.6.0 + Library + CUETools.Codecs.FLACCL + CUETools.Codecs.FLACCL + 7.0 + true + true + false + $(NoWarn);WFO1000 + ..\bin\$(Configuration)\$(TargetFramework)\plugins\ + false + $(BaseIntermediateOutputPath)\$(Configuration)\$(TargetFramework)\ + + + + + False + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/CUETools.Codecs.FLACCL/Properties/Resources.Designer.cs b/CUETools.Codecs.FLACCL/Properties/Resources.Designer.cs index b43ad8e85..6f341093c 100644 --- a/CUETools.Codecs.FLACCL/Properties/Resources.Designer.cs +++ b/CUETools.Codecs.FLACCL/Properties/Resources.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18033 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -19,7 +19,7 @@ namespace CUETools.Codecs.FLACCL.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/CUETools.Codecs.Flake/CUETools.Codecs.Flake.csproj b/CUETools.Codecs.Flake/CUETools.Codecs.Flake.csproj index 3f1b647db..dc45658ec 100644 --- a/CUETools.Codecs.Flake/CUETools.Codecs.Flake.csproj +++ b/CUETools.Codecs.Flake/CUETools.Codecs.Flake.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Codecs.Flake CUETools.Codecs.Flake diff --git a/CUETools.Codecs.HDCD/CUETools.Codecs.HDCD.csproj b/CUETools.Codecs.HDCD/CUETools.Codecs.HDCD.csproj index 998621d09..d7acaccba 100644 --- a/CUETools.Codecs.HDCD/CUETools.Codecs.HDCD.csproj +++ b/CUETools.Codecs.HDCD/CUETools.Codecs.HDCD.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Codecs.HDCD CUETools.Codecs.HDCD diff --git a/CUETools.Codecs.HDCD/HDCDDLL.cs b/CUETools.Codecs.HDCD/HDCDDLL.cs index c0750fb8c..cac612ae4 100644 --- a/CUETools.Codecs.HDCD/HDCDDLL.cs +++ b/CUETools.Codecs.HDCD/HDCDDLL.cs @@ -53,7 +53,7 @@ static HDCDDLL() var myFolder = System.IO.Path.GetDirectoryName(myPath); var is64 = IntPtr.Size == 8; var subfolder = is64 ? "x64" : "win32"; -#if NET47 +#if NET48 IntPtr Dll = LoadLibrary(System.IO.Path.Combine(myFolder, subfolder, DllName + ".dll")); #else IntPtr Dll = LoadLibrary(System.IO.Path.Combine(System.IO.Path.Combine(myFolder, subfolder), DllName + ".dll")); diff --git a/CUETools.Codecs.Icecast/CUETools.Codecs.Icecast.csproj b/CUETools.Codecs.Icecast/CUETools.Codecs.Icecast.csproj index 00c6d013a..219bf91f6 100644 --- a/CUETools.Codecs.Icecast/CUETools.Codecs.Icecast.csproj +++ b/CUETools.Codecs.Icecast/CUETools.Codecs.Icecast.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Codecs.Icecast CUETools.Codecs.Icecast diff --git a/CUETools.Codecs.LossyWAV/CUETools.Codecs.LossyWAV.csproj b/CUETools.Codecs.LossyWAV/CUETools.Codecs.LossyWAV.csproj index 43bcfaf0f..26b589703 100644 --- a/CUETools.Codecs.LossyWAV/CUETools.Codecs.LossyWAV.csproj +++ b/CUETools.Codecs.LossyWAV/CUETools.Codecs.LossyWAV.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Codecs.LossyWAV CUETools.Codecs.LossyWAV diff --git a/CUETools.Codecs.MACLib/CUETools.Codecs.MACLib.csproj b/CUETools.Codecs.MACLib/CUETools.Codecs.MACLib.csproj index ae90a69a0..936f0d38f 100644 --- a/CUETools.Codecs.MACLib/CUETools.Codecs.MACLib.csproj +++ b/CUETools.Codecs.MACLib/CUETools.Codecs.MACLib.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Codecs.MACLib CUETools.Codecs.MACLib diff --git a/CUETools.Codecs.MACLib/MACLibDll.cs b/CUETools.Codecs.MACLib/MACLibDll.cs index 30efa25e4..d5289c99c 100644 --- a/CUETools.Codecs.MACLib/MACLibDll.cs +++ b/CUETools.Codecs.MACLib/MACLibDll.cs @@ -72,7 +72,7 @@ static MACLibDll() var myFolder = System.IO.Path.GetDirectoryName(myPath); var is64 = IntPtr.Size == 8; var subfolder = is64 ? "x64" : "win32"; -#if NET47 +#if NET48 IntPtr Dll = LoadLibrary(System.IO.Path.Combine(myFolder, subfolder, DllName + ".dll")); #else IntPtr Dll = LoadLibrary(System.IO.Path.Combine(System.IO.Path.Combine(myFolder, subfolder), DllName + ".dll")); diff --git a/CUETools.Codecs.MPEG/CUETools.Codecs.MPEG.csproj b/CUETools.Codecs.MPEG/CUETools.Codecs.MPEG.csproj index 473bb4f92..7d043cbdf 100644 --- a/CUETools.Codecs.MPEG/CUETools.Codecs.MPEG.csproj +++ b/CUETools.Codecs.MPEG/CUETools.Codecs.MPEG.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Codecs.MPEG CUETools.Codecs.MPEG diff --git a/CUETools.Codecs.WMA/CUETools.Codecs.WMA.csproj b/CUETools.Codecs.WMA/CUETools.Codecs.WMA.csproj index 11e5be0a8..2f0e88be5 100644 --- a/CUETools.Codecs.WMA/CUETools.Codecs.WMA.csproj +++ b/CUETools.Codecs.WMA/CUETools.Codecs.WMA.csproj @@ -1,7 +1,7 @@  - net47;net20 + netstandard2.1 2.2.6.0 CUETools.Codecs.WMA CUETools.Codecs.WMA diff --git a/CUETools.Codecs.ffmpeg/CUETools.Codecs.ffmpeg.csproj b/CUETools.Codecs.ffmpeg/CUETools.Codecs.ffmpeg.csproj index b8bad433f..b386b79f2 100644 --- a/CUETools.Codecs.ffmpeg/CUETools.Codecs.ffmpeg.csproj +++ b/CUETools.Codecs.ffmpeg/CUETools.Codecs.ffmpeg.csproj @@ -1,7 +1,7 @@  - net47;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Codecs.ffmpegdll CUETools.Codecs.ffmpegdll @@ -25,7 +25,7 @@ - + diff --git a/CUETools.Codecs.lame_enc/CUETools.Codecs.LAME.csproj b/CUETools.Codecs.lame_enc/CUETools.Codecs.LAME.csproj index d95f4f801..ae7e56b63 100644 --- a/CUETools.Codecs.lame_enc/CUETools.Codecs.LAME.csproj +++ b/CUETools.Codecs.lame_enc/CUETools.Codecs.LAME.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Codecs.lame_enc CUETools.Codecs.lame_enc diff --git a/CUETools.Codecs.libFLAC/CUETools.Codecs.libFLAC.csproj b/CUETools.Codecs.libFLAC/CUETools.Codecs.libFLAC.csproj index f2bef093a..368e65b79 100644 --- a/CUETools.Codecs.libFLAC/CUETools.Codecs.libFLAC.csproj +++ b/CUETools.Codecs.libFLAC/CUETools.Codecs.libFLAC.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Codecs.libFLAC CUETools.Codecs.libFLAC diff --git a/CUETools.Codecs.libFLAC/FLACDLL.cs b/CUETools.Codecs.libFLAC/FLACDLL.cs index 8555e7b3b..37f123e44 100644 --- a/CUETools.Codecs.libFLAC/FLACDLL.cs +++ b/CUETools.Codecs.libFLAC/FLACDLL.cs @@ -163,7 +163,7 @@ static FLACDLL() var myFolder = System.IO.Path.GetDirectoryName(myPath); var is64 = IntPtr.Size == 8; var subfolder = is64 ? "x64" : "win32"; -#if NET47 +#if NET48 IntPtr Dll = LoadLibrary(System.IO.Path.Combine(myFolder, subfolder, DllName + ".dll")); #else IntPtr Dll = LoadLibrary(System.IO.Path.Combine(System.IO.Path.Combine(myFolder, subfolder), DllName + ".dll")); diff --git a/CUETools.Codecs.libmp3lame/CUETools.Codecs.libmp3lame.csproj b/CUETools.Codecs.libmp3lame/CUETools.Codecs.libmp3lame.csproj index 2e874255b..01205e4ef 100644 --- a/CUETools.Codecs.libmp3lame/CUETools.Codecs.libmp3lame.csproj +++ b/CUETools.Codecs.libmp3lame/CUETools.Codecs.libmp3lame.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Codecs.libmp3lame CUETools.Codecs.libmp3lame diff --git a/CUETools.Codecs.libmp3lame/libmp3lamedll.cs b/CUETools.Codecs.libmp3lame/libmp3lamedll.cs index 12a086658..42eae49f2 100644 --- a/CUETools.Codecs.libmp3lame/libmp3lamedll.cs +++ b/CUETools.Codecs.libmp3lame/libmp3lamedll.cs @@ -52,7 +52,7 @@ static libmp3lamedll() var myFolder = System.IO.Path.GetDirectoryName(myPath); var is64 = IntPtr.Size == 8; var subfolder = is64 ? "x64" : "win32"; -#if NET47 +#if NET48 IntPtr Dll = LoadLibrary(System.IO.Path.Combine(myFolder, subfolder, DllName + ".dll")); #else IntPtr Dll = LoadLibrary(System.IO.Path.Combine(System.IO.Path.Combine(myFolder, subfolder), DllName + ".dll")); diff --git a/CUETools.Codecs.libwavpack/CUETools.Codecs.libwavpack.csproj b/CUETools.Codecs.libwavpack/CUETools.Codecs.libwavpack.csproj index 364267a04..f20505199 100644 --- a/CUETools.Codecs.libwavpack/CUETools.Codecs.libwavpack.csproj +++ b/CUETools.Codecs.libwavpack/CUETools.Codecs.libwavpack.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Codecs.libwavpack CUETools.Codecs.libwavpack diff --git a/CUETools.Codecs.libwavpack/wavpackdll.cs b/CUETools.Codecs.libwavpack/wavpackdll.cs index a320d9dba..dc04b2531 100644 --- a/CUETools.Codecs.libwavpack/wavpackdll.cs +++ b/CUETools.Codecs.libwavpack/wavpackdll.cs @@ -71,7 +71,7 @@ static wavpackdll() var myFolder = System.IO.Path.GetDirectoryName(myPath); var is64 = IntPtr.Size == 8; var subfolder = is64 ? "x64" : "win32"; -#if NET47 +#if NET48 IntPtr Dll = LoadLibrary(System.IO.Path.Combine(myFolder, subfolder, DllName + ".dll")); #else IntPtr Dll = LoadLibrary(System.IO.Path.Combine(System.IO.Path.Combine(myFolder, subfolder), DllName + ".dll")); diff --git a/CUETools.Codecs/AudioEncoderSettings.cs b/CUETools.Codecs/AudioEncoderSettings.cs index 6d86eb32c..a0650dae5 100644 --- a/CUETools.Codecs/AudioEncoderSettings.cs +++ b/CUETools.Codecs/AudioEncoderSettings.cs @@ -1,20 +1,11 @@ using System; using System.ComponentModel; -using System.Collections.Generic; -using System.Text; -using Newtonsoft.Json; - -#if NET20 - namespace System.Runtime.CompilerServices - { - [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class - | AttributeTargets.Method)] - public sealed class ExtensionAttribute : Attribute { } - } -#endif - -namespace CUETools.Codecs -{ +using System.Collections.Generic; +using System.Text; +using Newtonsoft.Json; + +namespace CUETools.Codecs +{ public interface IAudioEncoderSettings { string Name { get; } diff --git a/CUETools.Codecs/CUETools.Codecs.csproj b/CUETools.Codecs/CUETools.Codecs.csproj index baf595324..fb673685c 100644 --- a/CUETools.Codecs/CUETools.Codecs.csproj +++ b/CUETools.Codecs/CUETools.Codecs.csproj @@ -1,7 +1,7 @@  - net20;net47;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Codecs CUETools.Codecs @@ -17,7 +17,7 @@ - + diff --git a/CUETools.Compression.Rar/CUETools.Compression.Rar.csproj b/CUETools.Compression.Rar/CUETools.Compression.Rar.csproj index 86ec0cc82..b34838b62 100644 --- a/CUETools.Compression.Rar/CUETools.Compression.Rar.csproj +++ b/CUETools.Compression.Rar/CUETools.Compression.Rar.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Compression.Rar CUETools.Compression.Rar diff --git a/CUETools.Compression.Zip/CUETools.Compression.Zip.csproj b/CUETools.Compression.Zip/CUETools.Compression.Zip.csproj index ec4870c33..f00e9c809 100644 --- a/CUETools.Compression.Zip/CUETools.Compression.Zip.csproj +++ b/CUETools.Compression.Zip/CUETools.Compression.Zip.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Compression.Zip CUETools.Compression.Zip diff --git a/CUETools.Compression/CUETools.Compression.csproj b/CUETools.Compression/CUETools.Compression.csproj index 2b94c2cec..ae76599a1 100644 --- a/CUETools.Compression/CUETools.Compression.csproj +++ b/CUETools.Compression/CUETools.Compression.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Compression CUETools.Compression diff --git a/CUETools.Converter/CUETools.Converter.csproj b/CUETools.Converter/CUETools.Converter.csproj index 3f8e3c0ef..f0089738c 100644 --- a/CUETools.Converter/CUETools.Converter.csproj +++ b/CUETools.Converter/CUETools.Converter.csproj @@ -1,9 +1,7 @@  - net47;netcoreapp2.0 - false - 2.0.9 + net10.0 2.2.6.0 CUETools.Converter CUETools.Converter diff --git a/CUETools.DSP.Mixer/CUETools.DSP.Mixer.csproj b/CUETools.DSP.Mixer/CUETools.DSP.Mixer.csproj index 286423439..ce87b37c2 100644 --- a/CUETools.DSP.Mixer/CUETools.DSP.Mixer.csproj +++ b/CUETools.DSP.Mixer/CUETools.DSP.Mixer.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.DSP.Mixer CUETools.DSP.Mixer diff --git a/CUETools.DSP.Resampler/CUETools.DSP.Resampler.csproj b/CUETools.DSP.Resampler/CUETools.DSP.Resampler.csproj index 0b3fd772f..99961d8d1 100644 --- a/CUETools.DSP.Resampler/CUETools.DSP.Resampler.csproj +++ b/CUETools.DSP.Resampler/CUETools.DSP.Resampler.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.DSP.Resampler CUETools.DSP.Resampler diff --git a/CUETools.FLACCL.cmd/App.config b/CUETools.FLACCL.cmd/App.config index c8d467dcf..0f9b4a6db 100644 --- a/CUETools.FLACCL.cmd/App.config +++ b/CUETools.FLACCL.cmd/App.config @@ -1,8 +1,8 @@ - + - \ No newline at end of file + diff --git a/CUETools.FLACCL.cmd/CUETools.FLACCL.cmd.csproj b/CUETools.FLACCL.cmd/CUETools.FLACCL.cmd.csproj index 8f870ca4e..a376e3085 100644 --- a/CUETools.FLACCL.cmd/CUETools.FLACCL.cmd.csproj +++ b/CUETools.FLACCL.cmd/CUETools.FLACCL.cmd.csproj @@ -1,114 +1,29 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {1FCA8834-34E6-47CF-B53F-D8DF35345363} - Exe - Properties - CUETools.FLACCL.cmd - CUETools.FLACCL.cmd - - - 3.5 - - - v4.7 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - true - full - false - ..\bin\Debug\net47\ - DEBUG;TRACE - prompt - 4 - AnyCPU - AllRules.ruleset - - - pdbonly - true - ..\bin\Release\net47\ - TRACE - prompt - 4 - AnyCPU - AllRules.ruleset - - - - False - ..\ThirdParty\openclnet\source\bin\$(Configuration)\net47\OpenCLNet.dll - - - - - - - - - - - - {082D6B9E-326E-4D15-9798-EDAE9EDE70A6} - CUETools.Codecs.Flake - False - - - {6458A13A-30EF-45A9-9D58-E5031B17BEE2} - CUETools.Codecs - False - - - {DFE55765-564C-4B8F-993B-A94C4D1C212E} - CUETools.Codecs.FLACCL - False - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - \ No newline at end of file + + + + net10.0-windows + 2.2.6.0 + Exe + CUETools.FLACCL.cmd + CUETools.FLACCL.cmd + 7.0 + false + ..\bin\$(Configuration)\$(TargetFramework)\ + false + $(BaseIntermediateOutputPath)\$(Configuration)\$(TargetFramework)\ + + + + + False + + + + + + + + + + + diff --git a/CUETools.Flake/CUETools.Flake.csproj b/CUETools.Flake/CUETools.Flake.csproj index a68dde5a2..9c8e2389c 100644 --- a/CUETools.Flake/CUETools.Flake.csproj +++ b/CUETools.Flake/CUETools.Flake.csproj @@ -1,9 +1,7 @@  - net47;net20;netcoreapp2.0 - false - 2.0.9 + net10.0 2.2.6.0 CUETools.Flake CUETools.Flake diff --git a/CUETools.LossyWAV/CUETools.LossyWAV.csproj b/CUETools.LossyWAV/CUETools.LossyWAV.csproj index b7c3b0c69..5fc5d1169 100644 --- a/CUETools.LossyWAV/CUETools.LossyWAV.csproj +++ b/CUETools.LossyWAV/CUETools.LossyWAV.csproj @@ -1,9 +1,7 @@  - net47;net20;netcoreapp2.0 - false - 2.0.9 + net10.0 2.2.6.0 CUETools.LossyWAV CUETools.LossyWAV diff --git a/CUETools.Parity/CUETools.Parity.csproj b/CUETools.Parity/CUETools.Parity.csproj index 136e47052..12b6fd022 100644 --- a/CUETools.Parity/CUETools.Parity.csproj +++ b/CUETools.Parity/CUETools.Parity.csproj @@ -1,7 +1,7 @@  - net20;net47;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Parity CUETools.Parity diff --git a/CUETools.Processor/AccurateRipMeta.cs b/CUETools.Processor/AccurateRipMeta.cs new file mode 100644 index 000000000..d7317eddd --- /dev/null +++ b/CUETools.Processor/AccurateRipMeta.cs @@ -0,0 +1,414 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using CUETools.AccurateRip; +using CUETools.CDImage; +using CUETools.CTDB; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace CUETools.Processor +{ + public static class AccurateRipMeta + { + public const string SourceKey = "accurateripmeta"; + public const string DisplayName = "AccurateRip Meta"; + internal const string ContentType = "application/x-www-form-urlencoded"; + internal const string CoverArtHost = "meta.accuraterip.com"; + // HTTPS on meta.accuraterip.com:443 was refused during contract review, so the live endpoint remains HTTP-only. + public static readonly string Endpoint = "http://meta.accuraterip.com/discmatch"; + internal const int LookupTimeoutMilliseconds = 5000; + internal const int ReadWriteTimeoutMilliseconds = 15000; + + public static bool TryParseMetadata(string json, CDImageLayout toc, out CUEMetadataEntry entry) + { + entry = null; + + if (toc == null || string.IsNullOrWhiteSpace(json)) + return false; + + JObject response; + try + { + response = JObject.Parse(json); + } + catch (JsonException ex) + { + System.Diagnostics.Trace.WriteLine("AccurateRip Meta JSON parse failed: " + ex.Message); + return false; + } + + JArray tracks = response["tracks"] as JArray; + int audioTracks = (int)toc.AudioTracks; + if (tracks == null || tracks.Count < audioTracks) + return false; + + List orderedTracks = OrderTracksByTrackNumber(tracks, toc.FirstAudio, audioTracks); + if (orderedTracks == null) + return false; + + if (string.IsNullOrWhiteSpace(Clean(response["Artist"])) && string.IsNullOrWhiteSpace(Clean(response["Album"]))) + return false; + + var metadata = new CUEMetadata(toc.TOCID, audioTracks) + { + Artist = NormalizeProviderList(response["Artist"]), + Title = Clean(response["Album"]), + Year = Clean(response["Year"]), + Genre = NormalizeGenreList(response["Genre"]), + Barcode = CleanIdentifier(response["UPC"], "UPC"), + Label = Clean(response["Label"]), + LabelNo = CleanIdentifier(response["CatalogNum"], "CatalogNum") + }; + + // AccurateRip Meta also exposes Styles, Composers, Conductors, _albumid, and _tocid. Keep them unused until CUETools has explicit fields for them; do not overwrite Genre with Styles. + for (int i = 0; i < audioTracks; i++) + { + JObject track = orderedTracks[i]; + + metadata.Tracks[i].Title = Clean(track["Title"]); + string trackArtist = NormalizeProviderList(track["Artist"]); + metadata.Tracks[i].Artist = trackArtist != "" ? trackArtist : metadata.Artist; + metadata.Tracks[i].ISRC = CleanIdentifier(track["ISRC"], "ISRC"); + } + + string coverUrl = NormalizeCoverUrl(response["_arturl"]); + if (coverUrl != "") + { + metadata.AlbumArt.Add(new CTDBResponseMetaImage + { + uri = coverUrl, + uri150 = coverUrl, + primary = true + }); + } + + entry = new CUEMetadataEntry(metadata, toc, SourceKey); + return true; + } + + internal static string CreateRequestDiscId(CDImageLayout toc) + { + return string.Format(CultureInfo.InvariantCulture, "{0:000}-{1}", toc.AudioTracks, AccurateRipVerify.CalculateAccurateRipId(toc)); + } + + internal static string CreateRequestBody(string accurateRipDiscId) + { + return JsonConvert.SerializeObject(new AccurateRipMetaRequest + { + AccurateRipDiscId = accurateRipDiscId + }); + } + + private static string Clean(JToken value) + { + if (value == null || value.Type == JTokenType.Null || value.Type == JTokenType.Array || value.Type == JTokenType.Object) + return ""; + + JValue scalar = value as JValue; + // The live contract uses strings for identifiers such as UPC. Numeric coercion is fail-soft only and cannot preserve leading zeroes. + return scalar == null || scalar.Value == null ? "" : Convert.ToString(scalar.Value, CultureInfo.InvariantCulture).Trim(); + } + + private static string CleanIdentifier(JToken value, string fieldName) + { + if (value == null || value.Type == JTokenType.Null) + return ""; + + if (value.Type != JTokenType.String) + { + if (value.Type != JTokenType.Array && value.Type != JTokenType.Object) + System.Diagnostics.Trace.WriteLine("AccurateRip Meta ignored non-string " + fieldName + " value."); + return ""; + } + + return Clean(value); + } + + private static List OrderTracksByTrackNumber(JArray tracks, int firstAudioTrackNumber, int audioTracks) + { + List orderedTracks = OrderTracksByNumberRange(tracks, firstAudioTrackNumber, audioTracks); + if (orderedTracks != null || firstAudioTrackNumber == 1) + return orderedTracks; + + // Some providers normalize mixed-mode discs to audio-relative track numbers. + return OrderTracksByNumberRange(tracks, 1, audioTracks); + } + + private static List OrderTracksByNumberRange(JArray tracks, int firstTrackNumber, int audioTracks) + { + var byNumber = new Dictionary(); + foreach (JToken item in tracks) + { + JObject track = item as JObject; + if (track == null) + continue; + + int trackNumber; + if (!int.TryParse(Clean(track["TrackNumber"]), NumberStyles.Integer, CultureInfo.InvariantCulture, out trackNumber)) + continue; + + if (trackNumber < firstTrackNumber || trackNumber >= firstTrackNumber + audioTracks) + continue; + + int audioIndex = trackNumber - firstTrackNumber; + if (byNumber.ContainsKey(audioIndex)) + return null; + + byNumber.Add(audioIndex, track); + } + + if (byNumber.Count != audioTracks) + return null; + + return Enumerable.Range(0, audioTracks) + .Select(audioIndex => byNumber[audioIndex]) + .ToList(); + } + + private static string NormalizeCoverUrl(JToken value) + { + string cleaned = Clean(value); + if (cleaned == "") + return ""; + + Uri uri; + if (!Uri.TryCreate(cleaned, UriKind.Absolute, out uri)) + return ""; + + if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps) + return ""; + + if (!string.Equals(uri.Host, CoverArtHost, StringComparison.OrdinalIgnoreCase)) + { + System.Diagnostics.Trace.WriteLine("AccurateRip Meta ignored cover art URL from unexpected host: " + uri.Host); + return ""; + } + + return uri.AbsoluteUri; + } + + private static string NormalizeGenreList(JToken value) + { + string text = Clean(value); + if (string.IsNullOrWhiteSpace(text)) + return ""; + + return string.Join("; ", text + .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) + .Select(line => line.Trim()) + .Where(line => line.Length > 0) + .Distinct() + .ToArray()); + } + + private static string NormalizeProviderList(JToken value) + { + string text = Clean(value); + if (string.IsNullOrWhiteSpace(text)) + return ""; + + string[] names = text + .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) + .Select(line => line.Trim()) + .Where(line => line.Length > 0) + .Distinct() + .ToArray(); + + if (names.Length == 0) + return ""; + if (names.Length == 1) + return names[0]; + if (names.Length == 2) + return names[0] + " & " + names[1]; + + return string.Join(", ", names.Take(names.Length - 1).ToArray()) + " & " + names[names.Length - 1]; + } + } + + public sealed class AccurateRipMetaProvider + { + private readonly IAccurateRipMetaTransport transport; + + public AccurateRipMetaProvider() + : this(new AccurateRipMetaHttpTransport()) + { + } + + public AccurateRipMetaProvider(IAccurateRipMetaTransport transport) + { + this.transport = transport ?? throw new ArgumentNullException(nameof(transport)); + } + + public CUEMetadataEntry Lookup(CDImageLayout toc, IWebProxy proxy) + { + return Lookup(toc, proxy, null); + } + + public CUEMetadataEntry Lookup(CDImageLayout toc, IWebProxy proxy, Action checkStop) + { + if (toc == null) + return null; + + try + { + string accurateRipDiscId = AccurateRipMeta.CreateRequestDiscId(toc); + string body = AccurateRipMeta.CreateRequestBody(accurateRipDiscId); + string json = transport.Post(AccurateRipMeta.Endpoint, body, proxy, checkStop); + + return AccurateRipMeta.TryParseMetadata(json, toc, out CUEMetadataEntry entry) ? entry : null; + } + catch (Exception ex) when (IsCancellation(ex)) + { + throw; + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine("AccurateRip Meta lookup failed: " + ex.Message); + return null; + } + } + + private static bool IsCancellation(Exception ex) + { + return ex is StopException || ex is OperationCanceledException; + } + } + + public interface IAccurateRipMetaTransport + { + string Post(string url, string body, IWebProxy proxy, Action checkStop); + } + + public sealed class AccurateRipMetaHttpTransport : IAccurateRipMetaTransport + { + public string Post(string url, string body, IWebProxy proxy, Action checkStop) + { + var request = (HttpWebRequest)WebRequest.Create(url); + int[] stopped = new[] { 0 }; + int[] stopWatcherFinished = new[] { 0 }; + Thread stopWatcher = null; + try + { + stopWatcher = StartStopWatcher(request, checkStop, stopped, stopWatcherFinished); + request.Method = "POST"; + // The undocumented endpoint matched the EasyEACGUI client only with this browser-style UA during contract review. + request.UserAgent = "Mozilla/5.0"; + // The server expects the EasyEACGUI-compatible JSON body with this form content type. + request.ContentType = AccurateRipMeta.ContentType; + request.AllowAutoRedirect = false; + request.ServicePoint.Expect100Continue = false; + request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + request.Timeout = AccurateRipMeta.LookupTimeoutMilliseconds; + request.ReadWriteTimeout = AccurateRipMeta.ReadWriteTimeoutMilliseconds; + request.Proxy = proxy; + + byte[] bytes = Encoding.UTF8.GetBytes(body); + request.ContentLength = bytes.Length; + using (Stream requestStream = request.GetRequestStream()) + requestStream.Write(bytes, 0, bytes.Length); + + using (var response = (HttpWebResponse)request.GetResponse()) + using (Stream responseStream = response.GetResponseStream()) + return ReadResponseBody(responseStream); + } + catch (WebException ex) + { + var response = ex.Response as HttpWebResponse; + if (response != null && (int)response.StatusCode >= 300 && (int)response.StatusCode < 400) + System.Diagnostics.Trace.WriteLine("AccurateRip Meta moved; update CUETools (got " + response.StatusCode + " -> " + response.Headers["Location"] + ")"); + if (response != null) + response.Dispose(); + request.Abort(); + if (Volatile.Read(ref stopped[0]) != 0) + throw new StopException(); + throw; + } + catch + { + request.Abort(); + if (Volatile.Read(ref stopped[0]) != 0) + throw new StopException(); + throw; + } + finally + { + Volatile.Write(ref stopWatcherFinished[0], 1); + if (stopWatcher != null) + stopWatcher.Join(500); + } + } + + private static Thread StartStopWatcher(HttpWebRequest request, Action checkStop, int[] stopped, int[] finished) + { + if (checkStop == null) + return null; + + var stopWatcher = new Thread(() => + { + while (Volatile.Read(ref finished[0]) == 0) + { + try + { + checkStop(); + } + catch (Exception ex) when (IsCancellation(ex)) + { + Interlocked.Exchange(ref stopped[0], 1); + request.Abort(); + return; + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine("AccurateRip Meta stop watcher failed: " + ex.Message); + return; + } + + for (int i = 0; i < 10 && Volatile.Read(ref finished[0]) == 0; i++) + Thread.Sleep(10); + } + }); + stopWatcher.IsBackground = true; + stopWatcher.Name = "AccurateRip Meta stop watcher"; + stopWatcher.Start(); + return stopWatcher; + } + + private static string ReadResponseBody(Stream responseStream) + { + if (responseStream == null) + return ""; + + const int maxResponseBytes = 1024 * 1024; + byte[] buffer = new byte[8192]; + using (var memory = new MemoryStream()) + { + int read; + while ((read = responseStream.Read(buffer, 0, buffer.Length)) > 0) + { + if (memory.Length + read > maxResponseBytes) + throw new InvalidDataException("AccurateRip Meta response exceeded the maximum supported size."); + memory.Write(buffer, 0, read); + } + + return Encoding.UTF8.GetString(memory.ToArray()); + } + } + + private static bool IsCancellation(Exception ex) + { + return ex is StopException || ex is OperationCanceledException; + } + } + + internal sealed class AccurateRipMetaRequest + { + [JsonProperty("accurateripdiscid")] + public string AccurateRipDiscId { get; set; } + } +} diff --git a/CUETools.Processor/CUEConfigAdvanced.cs b/CUETools.Processor/CUEConfigAdvanced.cs index 95ef4923d..0dc31acd0 100644 --- a/CUETools.Processor/CUEConfigAdvanced.cs +++ b/CUETools.Processor/CUEConfigAdvanced.cs @@ -51,17 +51,19 @@ public void Init() Init(CUEProcessorPlugins.encs, CUEProcessorPlugins.decs); } - [DefaultValue("i"), Category("Freedb"), DisplayName("Email user")] - public string FreedbUser { get; set; } - - [DefaultValue("wont.tell"), Category("Freedb"), DisplayName("Email domain")] - public string FreedbDomain { get; set; } - - [DefaultValue("gnudb.gnudb.org"), Category("Freedb"), DisplayName("Site address")] - public string FreedbSiteAddress { get; set; } - - [DefaultValue(ProxyMode.System), Category("Proxy"), DisplayName("Proxy mode")] - public ProxyMode UseProxyMode { get; set; } +#if DORMANT_FREEDB + [DefaultValue("i"), Category("Freedb"), DisplayName("Email user")] + public string FreedbUser { get; set; } + + [DefaultValue("wont.tell"), Category("Freedb"), DisplayName("Email domain")] + public string FreedbDomain { get; set; } + + [DefaultValue("gnudb.gnudb.org"), Category("Freedb"), DisplayName("Site address")] + public string FreedbSiteAddress { get; set; } +#endif + + [DefaultValue(ProxyMode.System), Category("Proxy"), DisplayName("Proxy mode")] + public ProxyMode UseProxyMode { get; set; } [DefaultValue("127.0.0.1"), Category("Proxy"), DisplayName("Proxy server host")] public string ProxyServer { get; set; } diff --git a/CUETools.Processor/CUEMetadata.cs b/CUETools.Processor/CUEMetadata.cs index 8be378a9e..c1dab0464 100644 --- a/CUETools.Processor/CUEMetadata.cs +++ b/CUETools.Processor/CUEMetadata.cs @@ -176,7 +176,7 @@ public void Merge(CUEMetadata metadata, bool overwrite) { if ((overwrite || Tracks[i].Title == "") && metadata.Tracks[i].Title != "") Tracks[i].Title = metadata.Tracks[i].Title; if ((overwrite || Tracks[i].Artist == "") && metadata.Tracks[i].Artist != "") Tracks[i].Artist = metadata.Tracks[i].Artist; - if ((overwrite || Tracks[i].Comment == "") && metadata.Tracks[i].Artist != "") Tracks[i].Comment = metadata.Tracks[i].Comment; + if ((overwrite || Tracks[i].Comment == "") && metadata.Tracks[i].Comment != "") Tracks[i].Comment = metadata.Tracks[i].Comment; if ((overwrite || Tracks[i].ISRC == "") && metadata.Tracks[i].ISRC != "") Tracks[i].ISRC = metadata.Tracks[i].ISRC; } } @@ -250,23 +250,26 @@ public void CopyMetadata(CUEMetadata metadata) } } - public void FillFromFreedb(Freedb.CDEntry cdEntry, int firstAudio) - { - Year = cdEntry.Year; - Genre = cdEntry.Genre; - Artist = cdEntry.Artist; +#if DORMANT_FREEDB + // FreeDB metadata import and repair helpers are dormant. AccurateRip Meta is the active replacement provider. + public void FillFromFreedb(Freedb.CDEntry cdEntry, int firstAudio) + { + Year = cdEntry.Year; + Genre = cdEntry.Genre; + Artist = cdEntry.Artist; Title = cdEntry.Title; Comment = cdEntry.ExtendedData; for (int i = 0; i < Tracks.Count; i++) { Tracks[i].Title = cdEntry.Tracks[i + firstAudio].Title; Tracks[i].Artist = cdEntry.Artist; - Tracks[i].Comment = cdEntry.ExtendedData; - } - } - - public void FillFromCtdb(CUETools.CTDB.CTDBResponseMeta cdEntry, int firstAudio) - { + Tracks[i].Comment = cdEntry.ExtendedData; + } + } +#endif + + public void FillFromCtdb(CUETools.CTDB.CTDBResponseMeta cdEntry, int firstAudio) + { this.Year = cdEntry.year ?? ""; this.Genre = cdEntry.genre ?? ""; this.Artist = cdEntry.artist ?? ""; @@ -316,10 +319,12 @@ public void FillFromCtdb(CUETools.CTDB.CTDBResponseMeta cdEntry, int firstAudio) } } - private static string FreedbToEncoding(Encoding iso, Encoding def, ref bool changed, ref bool error, string s) - { - try - { +#if DORMANT_FREEDB + // FreeDB metadata import and repair helpers are dormant. AccurateRip Meta is the active replacement provider. + private static string FreedbToEncoding(Encoding iso, Encoding def, ref bool changed, ref bool error, string s) + { + try + { string res = def.GetString(iso.GetBytes(s)); changed |= res != s; return res; @@ -367,11 +372,12 @@ public bool FreedbToVarious() { Tracks[i].Artist = title; } - } - return found; - } - - public void UpdateArtist(string artist) + } + return found; + } +#endif + + public void UpdateArtist(string artist) { for (int i = 0; i < Tracks.Count; i++) if (Tracks[i].Artist == Artist) diff --git a/CUETools.Processor/CUESheet.cs b/CUETools.Processor/CUESheet.cs index d44c91cd0..41df6e9cd 100644 --- a/CUETools.Processor/CUESheet.cs +++ b/CUETools.Processor/CUESheet.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Drawing; -#if NET47 || NET20 -using System.Drawing.Drawing2D; -#endif -using System.Globalization; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Globalization; using System.IO; using System.Net; using System.Text; @@ -16,7 +14,9 @@ using CUETools.Codecs; using CUETools.Compression; using CUETools.Ripper; -using Freedb; +#if DORMANT_FREEDB +using Freedb; +#endif namespace CUETools.Processor { @@ -122,29 +122,36 @@ public CDImageLayout TOC } } - public List AlbumArt - { - get { return _albumArt; } - } - -#if NET47 || NET20 - public Image Cover - { - get - { + public List AlbumArt + { + get { return _albumArt; } + } + + /// + /// Test seam for overriding the AccurateRip Meta lookup provider. + /// + public AccurateRipMetaProvider AccurateRipMetaProvider { get; internal set; } + + public Image Cover + { + get + { if (AlbumArt == null) return null; var picture = AlbumArt.Find(x => x.Type == TagLib.PictureType.FrontCover) ?? AlbumArt.Find(x => x.Type != TagLib.PictureType.NotAPicture); if (picture == null) - return null; - using (MemoryStream imageStream = new MemoryStream(picture.Data.Data, 0, picture.Data.Count)) - try { return Image.FromStream(imageStream); } - catch { } - return null; - } - } -#endif + return null; + using (MemoryStream imageStream = new MemoryStream(picture.Data.Data, 0, picture.Data.Count)) + try + { + using (Image source = Image.FromStream(imageStream)) + return new Bitmap(source); + } + catch { } + return null; + } + } public List SourcePaths { @@ -534,11 +541,11 @@ public uint? MinDataTrackLength #region Constructor - public CUESheet(CUEConfig config) - { - _config = config; - _progress = new CUEToolsProgressEventArgs(); - _progress.cueSheet = this; + public CUESheet(CUEConfig config) + { + _config = config; + _progress = new CUEToolsProgressEventArgs(); + _progress.cueSheet = this; _attributes = new List(); _tracks = new List(); _trackFilenames = new List(); @@ -826,11 +833,21 @@ public void ScanLocalDB(string folder) cueSheet.Close(); } } - _localDB.Save(); - } - - public List LookupAlbumInfo(bool useCache, bool useCUE, bool useCTDB, CTDBMetadataSearch metadataSearch) - { + _localDB.Save(); + } + + /// + /// Test seam for overriding CTDB metadata lookup without network access. + /// + protected virtual IEnumerable LookupCtdbMetadata(CTDBMetadataSearch metadataSearch) + { + var ctdb = new CUEToolsDB(TOC, proxy); + ctdb.ContactDB(_config.advanced.CTDBServer, "CUETools " + CUEToolsVersion, null, false, false, metadataSearch); + return ctdb.Metadata; + } + + public List LookupAlbumInfo(bool useCache, bool useCUE, bool useCTDB, CTDBMetadataSearch metadataSearch) + { List Releases = new List(); CUEMetadata dbmeta = null; @@ -888,25 +905,41 @@ public List LookupAlbumInfo(bool useCache, bool useCUE, bool useCTDB, CT Releases.Add(new CUEMetadataEntry(entry.Metadata, TOC, "local") { cover = frontCover }); } - bool ctdbFound = false; - if (useCTDB) - { - ShowProgress("Looking up album via CTDB...", 0.0, null, null); - var ctdb = new CUEToolsDB(TOC, proxy); - ctdb.ContactDB(_config.advanced.CTDBServer, "CUETools " + CUEToolsVersion, null, false, false, metadataSearch); - foreach (var meta in ctdb.Metadata) - { - CUEMetadata metadata = new CUEMetadata(TOC.TOCID, (int)TOC.AudioTracks); - metadata.FillFromCtdb(meta, TOC.FirstAudio - 1); - CDImageLayout toc = TOC; // TocFromCDEntry(meta); - Releases.Add(new CUEMetadataEntry(metadata, toc, meta.source)); - ctdbFound = true; - } - } - - if (!ctdbFound && metadataSearch == CTDBMetadataSearch.Extensive) - { - ShowProgress("Looking up album via Freedb...", 0.0, null, null); +#if DORMANT_FREEDB + bool ctdbFound = false; +#endif + if (useCTDB) + { + ShowProgress("Looking up album via CTDB...", 0.0, null, null); + foreach (var meta in LookupCtdbMetadata(metadataSearch)) + { + CUEMetadata metadata = new CUEMetadata(TOC.TOCID, (int)TOC.AudioTracks); + metadata.FillFromCtdb(meta, TOC.FirstAudio - 1); + CDImageLayout toc = TOC; // TocFromCDEntry(meta); + Releases.Add(new CUEMetadataEntry(metadata, toc, meta.source)); +#if DORMANT_FREEDB + ctdbFound = true; +#endif + } + } + + if (metadataSearch == CTDBMetadataSearch.Extensive) + { + // Extensive deliberately adds AccurateRip Meta even when CTDB already returned metadata. + ShowProgress("Looking up album via AccurateRip Meta...", 0.0, null, null); + CheckStop(); + + AccurateRipMetaProvider provider = AccurateRipMetaProvider ?? new AccurateRipMetaProvider(); + CUEMetadataEntry accurateRipMetaEntry = provider.Lookup(TOC, proxy, CheckStopRequested); + if (accurateRipMetaEntry != null) + Releases.Add(accurateRipMetaEntry); + } + +#if DORMANT_FREEDB + // FreeDB lookup is dormant. AccurateRip Meta is the active replacement provider. + if (!ctdbFound && metadataSearch == CTDBMetadataSearch.Extensive) + { + ShowProgress("Looking up album via Freedb...", 0.0, null, null); FreedbHelper m_freedb = new FreedbHelper(); m_freedb.Proxy = proxy; @@ -965,17 +998,20 @@ public List LookupAlbumInfo(bool useCache, bool useCUE, bool useCTDB, CT catch (Exception ex) { if (ex is StopException) - throw ex; - } - } - - ShowProgress("", 0, null, null); - return Releases; - } - - public CDImageLayout TocFromCDEntry(CDEntry cdEntry) - { - CDImageLayout tocFromCDEntry = new CDImageLayout(); + throw ex; + } + } +#endif + + ShowProgress("", 0, null, null); + return Releases; + } + +#if DORMANT_FREEDB + // FreeDB TOC reconstruction is dormant with the FreeDB provider. + public CDImageLayout TocFromCDEntry(CDEntry cdEntry) + { + CDImageLayout tocFromCDEntry = new CDImageLayout(); for (int i = 0; i < cdEntry.Tracks.Count; i++) { if (i >= _toc.TrackCount) @@ -988,10 +1024,11 @@ public CDImageLayout TocFromCDEntry(CDEntry cdEntry) } if (tocFromCDEntry.TrackCount > 0 && tocFromCDEntry[1].IsAudio) tocFromCDEntry[1][0].Start = 0; - return tocFromCDEntry; - } - - public void Open(string pathIn) + return tocFromCDEntry; + } +#endif + + public void Open(string pathIn) { _inputPath = pathIn; _inputDir = Path.GetDirectoryName(_inputPath) ?? _inputPath; @@ -1828,15 +1865,15 @@ public void LoadAndResizeAlbumArt() using (MemoryStream imageStream = new MemoryStream(pic.Data.Data, 0, pic.Data.Count)) try { -#if NET47 || NET20 - var image = Image.FromStream(ms); - pic.Description += $" ({image.Width}x{image.Height})"; - //if (image.Height > 0 && image.Width > 0 && (image.Height * 1.0 / image.Width) > 0.9 && (image.Width * 1.0 / image.Height) > 0.9) - // isSquare = true; - // pic.MimeType = f(image.RawFormat); -#endif - } - catch { } + using (var image = Image.FromStream(imageStream)) + { + pic.Description += $" ({image.Width}x{image.Height})"; + //if (image.Height > 0 && image.Width > 0 && (image.Height * 1.0 / image.Width) > 0.9 && (image.Width * 1.0 / image.Height) > 0.9) + // isSquare = true; + // pic.MimeType = f(image.RawFormat); + } + } + catch { } _albumArt.Add(pic); } @@ -1863,9 +1900,7 @@ public void LoadAndResizeAlbumArt() } } } -#if NET47 || NET20 - ResizeAlbumArt(); -#endif + ResizeAlbumArt(); } public void UseCUEToolsDB(string userAgent, string driveName, bool fuzzy, CTDBMetadataSearch metadataSearch) @@ -3030,9 +3065,8 @@ public CUEToolsLocalDBEntry OpenLocalDBEntry() return entry; } -#if NET47 || NET20 - private static Bitmap resizeImage(Image imgToResize, Size size) - { + private static Bitmap resizeImage(Image imgToResize, Size size) + { int sourceWidth = imgToResize.Width; int sourceHeight = imgToResize.Height; @@ -3051,16 +3085,15 @@ private static Bitmap resizeImage(Image imgToResize, Size size) int destWidth = (int)(sourceWidth * nPercent); int destHeight = (int)(sourceHeight * nPercent); - Bitmap b = new Bitmap(destWidth, destHeight); - Graphics g = Graphics.FromImage((Image)b); - g.InterpolationMode = InterpolationMode.HighQualityBicubic; - - g.DrawImage(imgToResize, 0, 0, destWidth, destHeight); - g.Dispose(); - - return b; - } -#endif + Bitmap b = new Bitmap(destWidth, destHeight); + using (Graphics g = Graphics.FromImage((Image)b)) + { + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.DrawImage(imgToResize, 0, 0, destWidth, destHeight); + } + + return b; + } public void ExtractAlbumArt() { @@ -3152,26 +3185,25 @@ private void LoadAlbumArt(TagLib.File fileInfo) using (MemoryStream imageStream = new MemoryStream(pic.Data.Data, 0, pic.Data.Count)) try { -#if NET47 || NET20 - var image = Image.FromStream(imageStream); - pic.Description += $" ({image.Width}x{image.Height})"; - if (image.Height > 0 && image.Width > 0 && (image.Height * 1.0 / image.Width) > 0.9 && (image.Width * 1.0 / image.Height) > 0.9) - { - if (isValidName) - pic.Type = TagLib.PictureType.FrontCover; - } - // pic.MimeType = f(image.RawFormat); -#endif - } - catch { } + using (var image = Image.FromStream(imageStream)) + { + pic.Description += $" ({image.Width}x{image.Height})"; + if (image.Height > 0 && image.Width > 0 && (image.Height * 1.0 / image.Width) > 0.9 && (image.Width * 1.0 / image.Height) > 0.9) + { + if (isValidName) + pic.Type = TagLib.PictureType.FrontCover; + } + // pic.MimeType = f(image.RawFormat); + } + } + catch { } _albumArt.Add(pic); ShowProgress(String.Format("Number of image files to process: {0} ({1:00})%", files.Count, (int)(100 * (++lineNumber + 0.0) / files.Count)), 0.0, null, null); } } } -#if NET47 || NET20 - public void ResizeAlbumArt() + public void ResizeAlbumArt() { if (_albumArt == null) return; @@ -3199,10 +3231,9 @@ public void ResizeAlbumArt() catch { } - } -#endif - - public string WriteReport() + } + + public string WriteReport() { if (isUsingAccurateRip || isUsingCUEToolsDB) { @@ -4110,10 +4141,10 @@ private int[] CalculateAudioFileLengths(CUEStyle style) return fileLengths; } - public void CheckStop() - { - lock (this) - { + public void CheckStop() + { + lock (this) + { if (_stop) throw new StopException(); if (_pause) @@ -4121,10 +4152,19 @@ public void CheckStop() ShowProgress("Paused...", 0, null, null); Monitor.Wait(this); } - } - } - - public void Stop() + } + } + + private void CheckStopRequested() + { + lock (this) + { + if (_stop) + throw new StopException(); + } + } + + public void Stop() { lock (this) { diff --git a/CUETools.Processor/CUETools.Processor.csproj b/CUETools.Processor/CUETools.Processor.csproj index 9c4375f32..5251083bf 100644 --- a/CUETools.Processor/CUETools.Processor.csproj +++ b/CUETools.Processor/CUETools.Processor.csproj @@ -1,7 +1,7 @@  - net47;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Processor CUETools.Processor @@ -22,18 +22,19 @@ - - - + + + + - - - + + + + - true diff --git a/CUETools.Processor/Properties/AssemblyInfo.cs b/CUETools.Processor/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..1b49b2c4b --- /dev/null +++ b/CUETools.Processor/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("CUETools.TestProcessor")] diff --git a/CUETools.Ripper.Console/CUETools.ConsoleRipper.csproj b/CUETools.Ripper.Console/CUETools.ConsoleRipper.csproj index 0fcc017aa..cf4cde0e2 100644 --- a/CUETools.Ripper.Console/CUETools.ConsoleRipper.csproj +++ b/CUETools.Ripper.Console/CUETools.ConsoleRipper.csproj @@ -1,7 +1,7 @@  - net47;net20 + net10.0 2.2.6.0 CUETools.Ripper.Console CUETools.Ripper.Console diff --git a/CUETools.Ripper.SCSI/CUETools.Ripper.SCSI.csproj b/CUETools.Ripper.SCSI/CUETools.Ripper.SCSI.csproj index b22030687..1d6133ee0 100644 --- a/CUETools.Ripper.SCSI/CUETools.Ripper.SCSI.csproj +++ b/CUETools.Ripper.SCSI/CUETools.Ripper.SCSI.csproj @@ -1,7 +1,7 @@  - net47;net20 + netstandard2.1 2.2.6.0 CUETools.Ripper.SCSI CUETools.Ripper.SCSI diff --git a/CUETools.Ripper/CUETools.Ripper.csproj b/CUETools.Ripper/CUETools.Ripper.csproj index e28e6b3e1..37ec076ba 100644 --- a/CUETools.Ripper/CUETools.Ripper.csproj +++ b/CUETools.Ripper/CUETools.Ripper.csproj @@ -1,7 +1,7 @@  - net47;net20;netstandard2.0 + netstandard2.1 2.2.6.0 CUETools.Ripper CUETools.Ripper diff --git a/CUETools.TestHelpers/CUETools.TestHelpers.csproj b/CUETools.TestHelpers/CUETools.TestHelpers.csproj index a481ec7a9..ad13db1d0 100644 --- a/CUETools.TestHelpers/CUETools.TestHelpers.csproj +++ b/CUETools.TestHelpers/CUETools.TestHelpers.csproj @@ -1,110 +1,25 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {84EBB7CB-02C9-40A9-9D32-C641B822DAA2} - Library - Properties - CUETools.TestHelpers - CUETools.TestHelpers - v4.7 - 512 - - - 3.5 - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - true - full - false - ..\bin\Debug\net47\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - ..\bin\Release\net47\ - TRACE - prompt - 4 - AllRules.ruleset - - - - - - - - - - - - - - {5802C7E9-157E-4124-946D-70B5AE48A5A1} - CUETools.AccurateRip - False - - - {1DD41038-D885-46C5-8DDE-E0B82F066584} - CUETools.CDImage - False - - - {6458A13A-30EF-45A9-9D58-E5031B17BEE2} - CUETools.Codecs - False - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - \ No newline at end of file + + + + netstandard2.1 + 1.0.0.0 + Library + CUETools.TestHelpers + CUETools.TestHelpers + false + ..\bin\$(Configuration)\ + + + + + False + + + + + + + + + + diff --git a/CUETools.TestHelpers/app.config b/CUETools.TestHelpers/app.config index 957cb1382..6b2c6ac27 100644 --- a/CUETools.TestHelpers/app.config +++ b/CUETools.TestHelpers/app.config @@ -1,6 +1,6 @@ - + diff --git a/CUETools.eac3to/CUETools.eac3to.csproj b/CUETools.eac3to/CUETools.eac3to.csproj index cb4c0acc2..cbc402221 100644 --- a/CUETools.eac3to/CUETools.eac3to.csproj +++ b/CUETools.eac3to/CUETools.eac3to.csproj @@ -1,9 +1,7 @@  - net47;netcoreapp2.0 - false - 2.0.9 + net10.0 2.2.6.0 CUETools.eac3to CUETools.eac3to diff --git a/CUETools.eac3ui/App.config b/CUETools.eac3ui/App.config index e8460b80e..0561a2203 100644 --- a/CUETools.eac3ui/App.config +++ b/CUETools.eac3ui/App.config @@ -6,7 +6,7 @@ - + diff --git a/CUETools.eac3ui/BLUTools.csproj b/CUETools.eac3ui/BLUTools.csproj index 590aa97d9..1dc1393df 100644 --- a/CUETools.eac3ui/BLUTools.csproj +++ b/CUETools.eac3ui/BLUTools.csproj @@ -1,194 +1,42 @@ - - - + + - Debug - AnyCPU - {678875A6-6B3A-4AAC-83B6-D9A72D9016CF} + net10.0-windows WinExe - BluTools BluTools - v4.7 - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - true - - - - AnyCPU - true - full - false - ..\bin\Debug\net47\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - ..\bin\Release\net47\ - TRACE - prompt - 4 - false - - + BluTools Icons\cue2.ico + 7.0 + true + true + false + ..\bin\$(Configuration)\ + - - - - - - - - - - - - 4.0 - - - - + + + + + + + + - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - App.xaml - Code - - - MainWindow.xaml - Code - + + + - - Code - - - True - True - Resources.resx - - - True - Settings.settings - True - - + ResXFileCodeGenerator Resources.Designer.cs - Designer - + SettingsSingleFileGenerator Settings.Designer.cs - - - - - - {1dd41038-d885-46c5-8dde-e0b82f066584} - CUETools.CDImage - - - {e75f7ccd-4266-42e1-a039-dc7eb5edd8f6} - CUETools.Codecs.MPEG - False - - - {082d6b9e-326e-4d15-9798-edae9ede70a6} - CUETools.Codecs.Flake - False - - - {6458a13a-30ef-45a9-9d58-e5031b17bee2} - CUETools.Codecs - - - {c36efaa5-4f81-4a29-800d-ba866ff13bc8} - CUETools.CTDB.Types - - - {aa2a9a7e-45fb-4632-ad85-85b0e556f818} - CUETools.CTDB - - - {4911bd82-49ef-4858-8b51-5394f86739a4} - CUETools.Processor - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + diff --git a/CUETools.eac3ui/Properties/Resources.Designer.cs b/CUETools.eac3ui/Properties/Resources.Designer.cs index c9872e05a..95a11a94e 100644 --- a/CUETools.eac3ui/Properties/Resources.Designer.cs +++ b/CUETools.eac3ui/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace BluTools.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/CUETools.eac3ui/Properties/Settings.Designer.cs b/CUETools.eac3ui/Properties/Settings.Designer.cs index b0899f9ab..bc1ad4c4a 100644 --- a/CUETools.eac3ui/Properties/Settings.Designer.cs +++ b/CUETools.eac3ui/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace BluTools.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "18.6.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/CUETools.sln b/CUETools.sln index 32216adf9..aff68687d 100644 --- a/CUETools.sln +++ b/CUETools.sln @@ -32,7 +32,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Third party binaries", "Thi ThirdParty\CSScriptLibrary.v1.1.dll = ThirdParty\CSScriptLibrary.v1.1.dll ThirdParty\CUDA.NET.dll = ThirdParty\CUDA.NET.dll ThirdParty\CUDA.NET.xml = ThirdParty\CUDA.NET.xml - ThirdParty\Freedb.dll = ThirdParty\Freedb.dll ThirdParty\ICSharpCode.SharpZipLib.dll = ThirdParty\ICSharpCode.SharpZipLib.dll ThirdParty\MusicBrainz.dll = ThirdParty\MusicBrainz.dll EndProjectSection @@ -91,8 +90,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUETools.AccurateRip", "CUE EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CUERipper", "CUERipper\CUERipper.csproj", "{39B43BBB-BAFC-4D85-9BEA-3BCB7EFED89C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Freedb", "Freedb\Freedb.csproj", "{5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TTALib", "ttalib-1.1\TTALib.vcxproj", "{B3DF599C-1C8F-451D-91E4-DD766210DA1F}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CUETools.Codecs.TTA", "CUETools.Codecs.TTA\CUETools.Codecs.TTA.vcxproj", "{1D1E99BC-6D22-41C0-BD94-FF4DD5EC725B}" @@ -345,14 +342,6 @@ Global {39B43BBB-BAFC-4D85-9BEA-3BCB7EFED89C}.Release|Any CPU.Build.0 = Release|Any CPU {39B43BBB-BAFC-4D85-9BEA-3BCB7EFED89C}.Release|Win32.ActiveCfg = Release|Any CPU {39B43BBB-BAFC-4D85-9BEA-3BCB7EFED89C}.Release|x64.ActiveCfg = Release|Any CPU - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4}.Debug|Win32.ActiveCfg = Debug|Any CPU - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4}.Debug|x64.ActiveCfg = Debug|Any CPU - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4}.Release|Any CPU.Build.0 = Release|Any CPU - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4}.Release|Win32.ActiveCfg = Release|Any CPU - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4}.Release|x64.ActiveCfg = Release|Any CPU {B3DF599C-1C8F-451D-91E4-DD766210DA1F}.Debug|Any CPU.ActiveCfg = Debug|Win32 {B3DF599C-1C8F-451D-91E4-DD766210DA1F}.Debug|Win32.ActiveCfg = Debug|Win32 {B3DF599C-1C8F-451D-91E4-DD766210DA1F}.Debug|Win32.Build.0 = Debug|Win32 @@ -796,7 +785,6 @@ Global {8CF07381-BEA2-4AFC-B3DD-9B2F21C65A3A} = {90FD290C-5D65-42A6-AC9C-928730432116} {9253A314-1821-42BF-B02F-2BF986B1765D} = {4B59E09C-A51F-4B80-91BE-987904DCEF7D} {39B43BBB-BAFC-4D85-9BEA-3BCB7EFED89C} = {90FD290C-5D65-42A6-AC9C-928730432116} - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4} = {7E402406-7E51-4F0D-8209-60824C1CD6E8} {B3DF599C-1C8F-451D-91E4-DD766210DA1F} = {8B179853-B7D6-479C-B8B2-6CBCE835D040} {1D1E99BC-6D22-41C0-BD94-FF4DD5EC725B} = {39A17A65-E893-44B8-A312-DDCDD990D9D1} {115CC5B0-0385-41CD-8A23-6A7EA4C51926} = {4B59E09C-A51F-4B80-91BE-987904DCEF7D} diff --git a/CUETools/CUETools.TestCodecs/CUETools.TestCodecs.csproj b/CUETools/CUETools.TestCodecs/CUETools.TestCodecs.csproj index b86e414f6..aca2389de 100644 --- a/CUETools/CUETools.TestCodecs/CUETools.TestCodecs.csproj +++ b/CUETools/CUETools.TestCodecs/CUETools.TestCodecs.csproj @@ -1,191 +1,65 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {04945FB2-8410-4F14-8262-2ED18DCDACD6} - Library - CUETools.TestCodecs - CUETools.TestCodecs - 4 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - 3.5 - - - v4.7 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - true - full - false - ..\..\bin\$(Configuration)\net47\tests\ - DEBUG;TRACE - true - AllRules.ruleset - false - - - true - true - ..\..\bin\$(Configuration)\net47\tests\ - TRACE - pdbonly - true - AllRules.ruleset - false - - - - False - ..\..\ThirdParty\CSScriptLibrary.v1.1.dll - False - - - - - - - - - - - - - - - - - - - - - - {5802C7E9-157E-4124-946D-70B5AE48A5A1} - CUETools.AccurateRip - False - - - {1DD41038-D885-46C5-8DDE-E0B82F066584} - CUETools.CDImage - False - - - {F2EC7193-D5E5-4252-9803-5CEB407E910F} - CUETools.Codecs.ALAC - True - - - {082D6B9E-326E-4D15-9798-EDAE9EDE70A6} - CUETools.Codecs.Flake - True - - - {f10ab92c-9afb-4fc0-94e0-06fcd3ca8155} - CUETools.Codecs.libFLAC - - - {8A0426FA-0BC2-4C49-A6E5-1F9A68156F19} - CUETools.Codecs.LossyWAV - False - - - {6458A13A-30EF-45A9-9D58-E5031B17BEE2} - CUETools.Codecs - True - - - {14EE067E-C218-4625-9540-2361AB27C4A6} - CUETools.Compression - False - - - {A6303861-CA06-4C2C-A104-BA9291538F6F} - CUETools.DSP.Resampler - False - - - {D2700165-3E77-4B28-928D-551F5FC11954} - CUETools.Ripper - False - - - {84EBB7CB-02C9-40A9-9D32-C641B822DAA2} - CUETools.TestHelpers - False - - - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4} - Freedb - False - - - - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - \ No newline at end of file + + + + net10.0-windows + Library + CUETools.TestCodecs + CUETools.TestCodecs + true + true + false + false + true + $(NoWarn);WFO1000 + ..\..\bin\$(Configuration)\$(TargetFramework)\tests\ + false + $(BaseIntermediateOutputPath)\$(Configuration)\$(TargetFramework)\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CUETools/CUETools.TestParity/CUETools.TestParity.csproj b/CUETools/CUETools.TestParity/CUETools.TestParity.csproj index 9916acbee..d316ef0d7 100644 --- a/CUETools/CUETools.TestParity/CUETools.TestParity.csproj +++ b/CUETools/CUETools.TestParity/CUETools.TestParity.csproj @@ -1,134 +1,40 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {6C74652F-1EF4-459E-84F4-99D93D3D17DA} - Library - Properties - CUETools.TestParity - CUETools.TestParity - v4.7 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - 3.5 - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - true - full - false - ..\..\bin\$(Configuration)\net47\tests\ - DEBUG;TRACE - prompt - 4 - true - AllRules.ruleset - false - - - pdbonly - true - ..\..\bin\$(Configuration)\net47\tests\ - TRACE - prompt - 4 - true - AllRules.ruleset - false - - - - - - - - - - - - - - - - - - {5802C7E9-157E-4124-946D-70B5AE48A5A1} - CUETools.AccurateRip - False - - - {1DD41038-D885-46C5-8DDE-E0B82F066584} - CUETools.CDImage - False - - - {6458A13A-30EF-45A9-9D58-E5031B17BEE2} - CUETools.Codecs - False - - - {ECEB839C-171B-4535-958F-9899310A0342} - CUETools.Parity - False - - - {84EBB7CB-02C9-40A9-9D32-C641B822DAA2} - CUETools.TestHelpers - False - - - - - - - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - \ No newline at end of file + + + + net10.0-windows + Library + CUETools.TestParity + CUETools.TestParity + true + false + false + true + ..\..\bin\$(Configuration)\$(TargetFramework)\tests\ + false + $(BaseIntermediateOutputPath)\$(Configuration)\$(TargetFramework)\ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CUETools/CUETools.TestParity/app.config b/CUETools/CUETools.TestParity/app.config index 957cb1382..6b2c6ac27 100644 --- a/CUETools/CUETools.TestParity/app.config +++ b/CUETools/CUETools.TestParity/app.config @@ -1,6 +1,6 @@ - + diff --git a/CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs b/CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs new file mode 100644 index 000000000..5a6abf41c --- /dev/null +++ b/CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs @@ -0,0 +1,818 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using CUETools.AccurateRip; +using CUETools.CDImage; +using CUETools.CTDB; +using CUETools.Processor; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace CUETools.TestProcessor +{ + [TestClass] + public class AccurateRipMetaTest + { + [TestMethod] + public void TryParseMetadataMapsRecordedAmarokFixture() + { + string json = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Fixtures", "accuraterip-meta-amarok.json")); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateAmarokToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.IsNotNull(entry); + Assert.AreEqual(AccurateRipMeta.SourceKey, entry.ImageKey); + Assert.AreEqual("Mike Oldfield", entry.metadata.Artist); + Assert.AreEqual("Amarok", entry.metadata.Title); + Assert.AreEqual("1990", entry.metadata.Year); + Assert.AreEqual("Parlata", entry.metadata.Genre); + Assert.AreEqual("Virgin", entry.metadata.Label); + Assert.AreEqual("CDV 2640", entry.metadata.LabelNo); + Assert.AreEqual("5012981264024", entry.metadata.Barcode); + Assert.AreEqual(1, entry.metadata.Tracks.Count); + Assert.AreEqual("Amarok", entry.metadata.Tracks[0].Title); + Assert.AreEqual("Mike Oldfield", entry.metadata.Tracks[0].Artist); + Assert.AreEqual("GBAAA0000417", entry.metadata.Tracks[0].ISRC); + Assert.AreEqual(1, entry.metadata.AlbumArt.Count); + Assert.AreEqual("http://meta.accuraterip.com/albumart/0000001F89DA34F26C1A", entry.metadata.AlbumArt[0].uri); + } + + [TestMethod] + public void TryParseMetadataMapsKnownFields() + { + bool parsed = AccurateRipMeta.TryParseMetadata(SampleJson(trackCount: 2), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.IsNotNull(entry); + Assert.AreEqual(AccurateRipMeta.SourceKey, entry.ImageKey); + Assert.AreEqual("The Artist & Guest", entry.metadata.Artist); + Assert.AreEqual("The Album", entry.metadata.Title); + Assert.AreEqual("1999", entry.metadata.Year); + Assert.AreEqual("Rock; Alternative", entry.metadata.Genre); + Assert.AreEqual("0123456789012", entry.metadata.Barcode); + Assert.AreEqual("The Label", entry.metadata.Label); + Assert.AreEqual("CAT-123", entry.metadata.LabelNo); + Assert.AreEqual("", entry.metadata.Comment); + Assert.AreEqual(2, entry.metadata.Tracks.Count); + Assert.AreEqual("Track One", entry.metadata.Tracks[0].Title); + Assert.AreEqual("Track Artist 1 & Other", entry.metadata.Tracks[0].Artist); + Assert.AreEqual("USAAA0100001", entry.metadata.Tracks[0].ISRC); + Assert.AreEqual("", entry.metadata.Tracks[0].Comment); + Assert.AreEqual("Track Two", entry.metadata.Tracks[1].Title); + Assert.AreEqual("Track Artist 2", entry.metadata.Tracks[1].Artist); + Assert.AreEqual("USAAA0100002", entry.metadata.Tracks[1].ISRC); + Assert.AreEqual(1, entry.metadata.AlbumArt.Count); + Assert.AreEqual("http://meta.accuraterip.com/albumart/front.jpg", entry.metadata.AlbumArt[0].uri); + Assert.AreEqual("http://meta.accuraterip.com/albumart/front.jpg", entry.metadata.AlbumArt[0].uri150); + Assert.IsTrue(entry.metadata.AlbumArt[0].primary); + } + + [TestMethod] + public void TryParseMetadataIgnoresFieldsWithoutStorage() + { + bool parsed = AccurateRipMeta.TryParseMetadata(SampleJson(trackCount: 2), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("Rock; Alternative", entry.metadata.Genre); + Assert.IsFalse(entry.metadata.Genre.Contains("Fusion")); + Assert.AreEqual("", entry.metadata.Comment); + Assert.IsFalse(entry.metadata.Comment.Contains("Fusion")); + Assert.IsFalse(entry.metadata.Tracks.Any(track => track.Comment.Contains("Composer"))); + Assert.IsFalse(entry.metadata.Tracks.Any(track => track.Comment.Contains("Conductor"))); + } + + [TestMethod] + public void TryParseMetadataFormatsMultipleProviderNames() + { + JObject json = JObject.Parse(SampleJson(trackCount: 2)); + json["Artist"] = "Artist One\rArtist Two\rArtist Three"; + + bool parsed = AccurateRipMeta.TryParseMetadata(json.ToString(), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("Artist One, Artist Two & Artist Three", entry.metadata.Artist); + } + + [TestMethod] + public void TryParseMetadataUsesAlbumArtistWhenTrackArtistMissing() + { + JObject json = JObject.Parse(SampleJson(trackCount: 2)); + JArray tracks = (JArray)json["tracks"]; + tracks[0]["Artist"].Parent.Remove(); + + bool parsed = AccurateRipMeta.TryParseMetadata(json.ToString(), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("The Artist & Guest", entry.metadata.Tracks[0].Artist); + Assert.AreEqual("Track Artist 2", entry.metadata.Tracks[1].Artist); + } + + [TestMethod] + public void TryParseMetadataOrdersTracksByTrackNumber() + { + JObject json = JObject.Parse(SampleJson(trackCount: 2)); + JArray tracks = (JArray)json["tracks"]; + json["tracks"] = new JArray(tracks[1], tracks[0]); + + bool parsed = AccurateRipMeta.TryParseMetadata(json.ToString(), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("Track One", entry.metadata.Tracks[0].Title); + Assert.AreEqual("Track Two", entry.metadata.Tracks[1].Title); + Assert.AreEqual("USAAA0100001", entry.metadata.Tracks[0].ISRC); + Assert.AreEqual("USAAA0100002", entry.metadata.Tracks[1].ISRC); + } + + [TestMethod] + public void TryParseMetadataRejectsMissingTrackNumber() + { + JObject json = JObject.Parse(SampleJson(trackCount: 2)); + JArray tracks = (JArray)json["tracks"]; + tracks[1]["TrackNumber"].Parent.Remove(); + + bool parsed = AccurateRipMeta.TryParseMetadata(json.ToString(), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsFalse(parsed); + Assert.IsNull(entry); + } + + [TestMethod] + public void TryParseMetadataRejectsTrackCountMismatch() + { + bool parsed = AccurateRipMeta.TryParseMetadata(SampleJson(trackCount: 1), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsFalse(parsed); + Assert.IsNull(entry); + } + + [TestMethod] + public void TryParseMetadataRejectsNonArrayTracksShape() + { + JObject json = JObject.Parse(SampleJson(trackCount: 2)); + json["tracks"] = new JObject { ["unexpected"] = "shape" }; + + bool parsed = AccurateRipMeta.TryParseMetadata(json.ToString(), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsFalse(parsed); + Assert.IsNull(entry); + } + + [TestMethod] + public void TryParseMetadataAllowsMissingCoverArt() + { + string json = SampleJson(trackCount: 2).Replace("\"_arturl\":\"http://meta.accuraterip.com/albumart/front.jpg\",", ""); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual(0, entry.metadata.AlbumArt.Count); + } + + [TestMethod] + public void TryParseMetadataAllowsHttpsCoverArtUrl() + { + string coverArtUrl = "https://meta.accuraterip.com/albumart/front.jpg"; + string json = SampleJsonWithCoverArtUrl(coverArtUrl); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual(1, entry.metadata.AlbumArt.Count); + Assert.AreEqual(coverArtUrl, entry.metadata.AlbumArt[0].uri); + Assert.AreEqual(coverArtUrl, entry.metadata.AlbumArt[0].uri150); + } + + [TestMethod] + [DataRow("not a url")] + [DataRow("file:///C:/covers/front.jpg")] + [DataRow("ftp://covers.example/front.jpg")] + [DataRow("http://covers.example/front.jpg")] + [DataRow("https://covers.example/front.jpg")] + [DataRow("//covers.example/front.jpg")] + [DataRow("/covers/front.jpg")] + public void TryParseMetadataRejectsInvalidCoverArtUrl(string coverArtUrl) + { + string json = SampleJsonWithCoverArtUrl(coverArtUrl); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual(0, entry.metadata.AlbumArt.Count); + } + + [TestMethod] + [DataRow("2", "2")] + [DataRow("abc", "2")] + [DataRow("0", "2")] + [DataRow("1", "3")] + public void TryParseMetadataRejectsInvalidTrackNumberSet(string firstTrackNumber, string secondTrackNumber) + { + string json = SampleJsonWithTrackNumbers(firstTrackNumber, secondTrackNumber); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsFalse(parsed); + Assert.IsNull(entry); + } + + [TestMethod] + public void TryParseMetadataUsesDiscTrackNumbersForDataTrackFirstDiscs() + { + string json = SampleJsonWithTrackNumbers("2", "3"); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateDataTrackFirstToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("Track One", entry.metadata.Tracks[0].Title); + Assert.AreEqual("Track Two", entry.metadata.Tracks[1].Title); + } + + [TestMethod] + public void TryParseMetadataAllowsAudioRelativeTrackNumbersForDataTrackFirstDiscs() + { + string json = SampleJsonWithTrackNumbers("1", "2"); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateDataTrackFirstToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("Track One", entry.metadata.Tracks[0].Title); + Assert.AreEqual("Track Two", entry.metadata.Tracks[1].Title); + } + + [TestMethod] + public void TryParseMetadataAllowsEmptyOptionalFields() + { + string json = SampleJson(trackCount: 2) + .Replace("\"Year\":\"1999\",", "\"Year\":\"\",") + .Replace("\"Genre\":\"Rock\\rAlternative\",", "\"Genre\":\"\",") + .Replace("\"Label\":\"The Label\",", "\"Label\":\"\",") + .Replace("\"CatalogNum\":\"CAT-123\",", "\"CatalogNum\":\"\",") + .Replace("\"UPC\":\"0123456789012\",", "\"UPC\":\"\","); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("", entry.metadata.Year); + Assert.AreEqual("", entry.metadata.Genre); + Assert.AreEqual("", entry.metadata.Label); + Assert.AreEqual("", entry.metadata.LabelNo); + Assert.AreEqual("", entry.metadata.Barcode); + } + + [TestMethod] + public void TryParseMetadataAllowsExtraProviderTracks() + { + bool parsed = AccurateRipMeta.TryParseMetadata(SampleJson(trackCount: 3), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual(2, entry.metadata.Tracks.Count); + Assert.AreEqual("Track One", entry.metadata.Tracks[0].Title); + Assert.AreEqual("Track Two", entry.metadata.Tracks[1].Title); + } + + [TestMethod] + public void TryParseMetadataAllowsUnorderedExtraProviderTracks() + { + JObject json = JObject.Parse(SampleJson(trackCount: 3)); + JArray tracks = (JArray)json["tracks"]; + json["tracks"] = new JArray(tracks[2], tracks[0], tracks[1]); + + bool parsed = AccurateRipMeta.TryParseMetadata(json.ToString(), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual(2, entry.metadata.Tracks.Count); + Assert.AreEqual("Track One", entry.metadata.Tracks[0].Title); + Assert.AreEqual("Track Two", entry.metadata.Tracks[1].Title); + } + + [TestMethod] + public void TryParseMetadataAllowsSurplusTrackWithoutTrackNumber() + { + JObject json = JObject.Parse(SampleJson(trackCount: 2)); + JArray tracks = (JArray)json["tracks"]; + tracks.Add(new JObject + { + ["Title"] = "Malformed Provider Extra Track", + ["Artist"] = "Ignored Extra Artist" + }); + + bool parsed = AccurateRipMeta.TryParseMetadata(json.ToString(), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual(2, entry.metadata.Tracks.Count); + Assert.AreEqual("Track One", entry.metadata.Tracks[0].Title); + Assert.AreEqual("Track Two", entry.metadata.Tracks[1].Title); + } + + [TestMethod] + public void TryParseMetadataAllowsSurplusNonObjectTrack() + { + JObject json = JObject.Parse(SampleJson(trackCount: 2)); + JArray tracks = (JArray)json["tracks"]; + tracks.Add("not a track object"); + + bool parsed = AccurateRipMeta.TryParseMetadata(json.ToString(), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual(2, entry.metadata.Tracks.Count); + Assert.AreEqual("Track One", entry.metadata.Tracks[0].Title); + Assert.AreEqual("Track Two", entry.metadata.Tracks[1].Title); + } + + [TestMethod] + public void TryParseMetadataAllowsOddScalarYearButDropsNumericIdentifiers() + { + JObject json = JObject.Parse(SampleJson(trackCount: 2)); + json["Year"] = 1999; + json["CatalogNum"] = 12345; + json["UPC"] = 123456789012; + ((JArray)json["tracks"])[0]["ISRC"] = 123456789012; + + bool parsed = AccurateRipMeta.TryParseMetadata(json.ToString(), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("1999", entry.metadata.Year); + Assert.AreEqual("", entry.metadata.LabelNo); + Assert.AreEqual("", entry.metadata.Barcode); + Assert.AreEqual("", entry.metadata.Tracks[0].ISRC); + } + + [TestMethod] + public void TryParseMetadataIgnoresOddCoverArtShapeWithoutDroppingMetadata() + { + string json = SampleJson(trackCount: 2).Replace("\"_arturl\":\"http://meta.accuraterip.com/albumart/front.jpg\"", "\"_arturl\":[\"http://meta.accuraterip.com/albumart/front.jpg\"]"); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("The Album", entry.metadata.Title); + Assert.AreEqual(0, entry.metadata.AlbumArt.Count); + } + + [TestMethod] + public void LookupPostsSerializedAccurateRipIdAndUsesProxy() + { + var toc = CreateTwoTrackToc(); + var proxy = new WebProxy("127.0.0.1", 8888); + var transport = new FakeTransport { Response = SampleJson(trackCount: 2) }; + var provider = new AccurateRipMetaProvider(transport); + + CUEMetadataEntry entry = provider.Lookup(toc, proxy); + + Assert.IsNotNull(entry); + Assert.AreEqual(1, transport.PostCount); + Assert.AreEqual(AccurateRipMeta.Endpoint, transport.Url); + Assert.AreSame(proxy, transport.Proxy); + var body = JsonConvert.DeserializeObject>(transport.Body); + Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "{0:000}-{1}", toc.AudioTracks, AccurateRipVerify.CalculateAccurateRipId(toc)), body["accurateripdiscid"]); + Assert.AreEqual(AccurateRipMeta.SourceKey, entry.ImageKey); + Assert.AreEqual("The Album", entry.metadata.Title); + } + + [TestMethod] + public async Task HttpTransportPostsLiveRequestShapeToLoopbackServer() + { + const string requestBody = "{\"accurateripdiscid\":\"001-test\"}"; + const string responseBody = "{}"; + + using (var listener = new TcpListener(IPAddress.Loopback, 0)) + { + listener.Start(); + int port = ((IPEndPoint)listener.LocalEndpoint).Port; + Task serverTask = ReadSingleRequestAsync(listener, responseBody); + Task clientTask = Task.Run(() => new AccurateRipMetaHttpTransport().Post("http://127.0.0.1:" + port + "/", requestBody, null, null)); + Task work = Task.WhenAll(serverTask, clientTask); + Task completed = await Task.WhenAny(work, Task.Delay(TimeSpan.FromSeconds(15))); + + Assert.AreSame(work, completed, "HTTP transport loopback test timed out."); + string response = await clientTask; + CapturedHttpRequest request = await serverTask; + + Assert.AreEqual(responseBody, response); + Assert.AreEqual("POST", request.Method); + Assert.AreEqual("Mozilla/5.0", request.UserAgent); + StringAssert.StartsWith(request.ContentType, "application/x-www-form-urlencoded"); + Assert.IsFalse(request.ExpectContinue); + Assert.AreEqual(requestBody, request.Body); + } + } + + [TestMethod] + public void LookupReturnsNullWhenTransportThrows() + { + var transport = new FakeTransport { Exception = new WebException("network unavailable") }; + var provider = new AccurateRipMetaProvider(transport); + + CUEMetadataEntry entry = provider.Lookup(CreateTwoTrackToc(), null); + + Assert.IsNull(entry); + Assert.AreEqual(1, transport.PostCount); + } + + [TestMethod] + public void LookupRethrowsStopExceptionFromTransport() + { + var transport = new FakeTransport { Exception = new StopException() }; + var provider = new AccurateRipMetaProvider(transport); + + try + { + provider.Lookup(CreateTwoTrackToc(), null); + Assert.Fail("Expected StopException."); + } + catch (StopException) + { + } + Assert.AreEqual(1, transport.PostCount); + } + + [TestMethod] + public void LookupReturnsNullForInvalidJson() + { + var transport = new FakeTransport { Response = "{not-json" }; + var provider = new AccurateRipMetaProvider(transport); + + CUEMetadataEntry entry = provider.Lookup(CreateTwoTrackToc(), null); + + Assert.IsNull(entry); + Assert.AreEqual(1, transport.PostCount); + } + + [TestMethod] + public void LookupReturnsNullForNullTocWithoutCallingTransport() + { + var transport = new FakeTransport { Response = SampleJson(trackCount: 2) }; + var provider = new AccurateRipMetaProvider(transport); + + CUEMetadataEntry entry = provider.Lookup(null, new WebProxy("127.0.0.1", 8888)); + + Assert.IsNull(entry); + Assert.AreEqual(0, transport.PostCount); + } + + [TestMethod] + public void LookupReturnsNullForInvalidMappedJson() + { + var transport = new FakeTransport { Response = SampleJson(trackCount: 1) }; + var provider = new AccurateRipMetaProvider(transport); + + CUEMetadataEntry entry = provider.Lookup(CreateTwoTrackToc(), null); + + Assert.IsNull(entry); + Assert.AreEqual(1, transport.PostCount); + } + + private static CDImageLayout CreateAmarokToc() + { + var sheet = new CUESheet(new CUEConfig()); + sheet.Open(AmarokCuePath()); + return sheet.TOC; + } + + private static string AmarokCuePath() + { + return Path.Combine(AppContext.BaseDirectory, "Amarok", "Amarok.cue"); + } + + private static CDImageLayout CreateTwoTrackToc() + { + var toc = new CDImageLayout(); + toc.AddTrack(new CDTrack(1, 0, 15000, true, false)); + toc.AddTrack(new CDTrack(2, 15000, 18000, true, false)); + return toc; + } + + private static CDImageLayout CreateDataTrackFirstToc() + { + var toc = new CDImageLayout(); + toc.AddTrack(new CDTrack(1, 0, 15000, false, false)); + toc.AddTrack(new CDTrack(2, 15000, 15000, true, false)); + toc.AddTrack(new CDTrack(3, 30000, 18000, true, false)); + return toc; + } + + private static string SampleJsonWithCoverArtUrl(string coverArtUrl) + { + JObject json = JObject.Parse(SampleJson(trackCount: 2)); + json["_arturl"] = coverArtUrl; + return json.ToString(); + } + + private static string SampleJsonWithTrackNumbers(params string[] trackNumbers) + { + JObject json = JObject.Parse(SampleJson(trackNumbers.Length)); + JArray tracks = (JArray)json["tracks"]; + + for (int i = 0; i < trackNumbers.Length; i++) + { + tracks[i]["TrackNumber"] = trackNumbers[i]; + } + + return json.ToString(); + } + + private static string SampleJson(int trackCount) + { + var tracks = new List + { + new + { + TrackNumber = "1", + Title = "Track One", + Artist = "Track Artist 1\rOther", + Composers = "Ignored Composer", + Conductors = "Ignored Conductor", + ISRC = "USAAA0100001" + } + }; + + if (trackCount > 1) + { + tracks.Add(new + { + TrackNumber = "2", + Title = "Track Two", + Artist = "Track Artist 2", + Composers = "Second Composer", + Conductors = "Second Conductor", + ISRC = "USAAA0100002" + }); + } + + if (trackCount > 2) + { + tracks.Add(new + { + TrackNumber = "3", + Title = "Provider Extra Track", + Artist = "Ignored Extra Artist", + Composers = "Ignored Extra Composer", + Conductors = "Ignored Extra Conductor", + ISRC = "USAAA0100003" + }); + } + + return JsonConvert.SerializeObject(new + { + _tocid = "toc-id", + Artist = "The Artist\rGuest", + Album = "The Album", + Year = "1999", + Genre = "Rock\rAlternative", + Label = "The Label", + Styles = "Fusion", + CatalogNum = "CAT-123", + UPC = "0123456789012", + _albumid = "album-id", + _arturl = "http://meta.accuraterip.com/albumart/front.jpg", + tracks = tracks + }); + } + + private static async Task ReadSingleRequestAsync(TcpListener listener, string responseBody) + { + using (TcpClient client = await listener.AcceptTcpClientAsync()) + using (NetworkStream stream = client.GetStream()) + { + byte[] rawRequest = await ReadRequestBytesThroughHeadersAsync(stream); + int headerEnd = FindHeaderEnd(rawRequest, rawRequest.Length); + string headerText = Encoding.ASCII.GetString(rawRequest, 0, headerEnd); + string[] headerLines = headerText.Split(new[] { "\r\n" }, StringSplitOptions.None); + + var captured = new CapturedHttpRequest(); + captured.Method = headerLines[0].Split(' ')[0]; + + int contentLength = 0; + bool expectsContinue = false; + for (int i = 1; i < headerLines.Length; i++) + { + int separator = headerLines[i].IndexOf(':'); + if (separator < 0) + continue; + + string name = headerLines[i].Substring(0, separator); + string value = headerLines[i].Substring(separator + 1).Trim(); + if (string.Equals(name, "User-Agent", StringComparison.OrdinalIgnoreCase)) + captured.UserAgent = value; + else if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) + captured.ContentType = value; + else if (string.Equals(name, "Content-Length", StringComparison.OrdinalIgnoreCase)) + int.TryParse(value, out contentLength); + else if (string.Equals(name, "Expect", StringComparison.OrdinalIgnoreCase) && value.IndexOf("100-continue", StringComparison.OrdinalIgnoreCase) >= 0) + { + expectsContinue = true; + captured.ExpectContinue = true; + } + } + + if (expectsContinue) + { + byte[] continueBytes = Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"); + await stream.WriteAsync(continueBytes, 0, continueBytes.Length); + } + + int bodyStart = headerEnd + 4; + byte[] bodyBytes = new byte[contentLength]; + int bufferedBodyBytes = Math.Min(contentLength, rawRequest.Length - bodyStart); + if (bufferedBodyBytes > 0) + Buffer.BlockCopy(rawRequest, bodyStart, bodyBytes, 0, bufferedBodyBytes); + + int offset = bufferedBodyBytes; + while (offset < contentLength) + { + int read = await stream.ReadAsync(bodyBytes, offset, contentLength - offset); + if (read == 0) + break; + + offset += read; + } + + captured.Body = Encoding.UTF8.GetString(bodyBytes, 0, offset); + + byte[] responseBodyBytes = Encoding.UTF8.GetBytes(responseBody); + string responseHeaders = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: " + responseBodyBytes.Length + "\r\nConnection: close\r\n\r\n"; + byte[] responseHeaderBytes = Encoding.ASCII.GetBytes(responseHeaders); + await stream.WriteAsync(responseHeaderBytes, 0, responseHeaderBytes.Length); + await stream.WriteAsync(responseBodyBytes, 0, responseBodyBytes.Length); + + return captured; + } + } + + private static async Task ReadRequestBytesThroughHeadersAsync(NetworkStream stream) + { + var request = new MemoryStream(); + byte[] buffer = new byte[1024]; + while (true) + { + int read = await stream.ReadAsync(buffer, 0, buffer.Length); + if (read == 0) + break; + + request.Write(buffer, 0, read); + byte[] bytes = request.ToArray(); + if (FindHeaderEnd(bytes, bytes.Length) >= 0) + return bytes; + } + + return request.ToArray(); + } + + private static int FindHeaderEnd(byte[] bytes, int length) + { + for (int i = 0; i <= length - 4; i++) + { + if (bytes[i] == '\r' && bytes[i + 1] == '\n' && bytes[i + 2] == '\r' && bytes[i + 3] == '\n') + return i; + } + + return -1; + } + + [TestMethod] + public void LookupAlbumInfoExtensiveAddsAccurateRipMeta() + { + var transport = new FakeTransport { Response = SampleJson(trackCount: 1) }; + var sheet = new CUESheet(new CUEConfig()) + { + AccurateRipMetaProvider = new AccurateRipMetaProvider(transport) + }; + sheet.Open(AmarokCuePath()); + + var releases = sheet.LookupAlbumInfo(false, false, false, CUETools.CTDB.CTDBMetadataSearch.Extensive) + .OfType() + .ToList(); + + Assert.AreEqual(1, releases.Count(entry => entry.ImageKey == AccurateRipMeta.SourceKey)); + var body = JsonConvert.DeserializeObject>(transport.Body); + Assert.AreEqual(AccurateRipMeta.CreateRequestDiscId(sheet.TOC), body["accurateripdiscid"]); + } + + [TestMethod] + public void LookupAlbumInfoExtensiveAddsAccurateRipMetaWhenCtdbReturnedMetadata() + { + var transport = new FakeTransport { Response = SampleJson(trackCount: 1) }; + var sheet = new CtdbStubCUESheet(new CUEConfig(), CreateCtdbMetadata()) + { + AccurateRipMetaProvider = new AccurateRipMetaProvider(transport) + }; + sheet.Open(AmarokCuePath()); + + var releases = sheet.LookupAlbumInfo(false, false, true, CTDBMetadataSearch.Extensive) + .OfType() + .ToList(); + + Assert.AreEqual(1, releases.Count(entry => entry.ImageKey == "ctdb")); + Assert.AreEqual(1, releases.Count(entry => entry.ImageKey == AccurateRipMeta.SourceKey)); + Assert.AreEqual(1, transport.PostCount); + } + + [TestMethod] + public void LookupAlbumInfoDefaultDoesNotQueryAccurateRipMeta() + { + var transport = new FakeTransport { Response = SampleJson(trackCount: 1) }; + var sheet = new CUESheet(new CUEConfig()) + { + AccurateRipMetaProvider = new AccurateRipMetaProvider(transport) + }; + sheet.Open(AmarokCuePath()); + + var releases = sheet.LookupAlbumInfo(false, false, false, CUETools.CTDB.CTDBMetadataSearch.Default) + .OfType() + .ToList(); + + Assert.AreEqual(0, releases.Count(entry => entry.ImageKey == AccurateRipMeta.SourceKey)); + Assert.IsNull(transport.Url); + } + + private sealed class CtdbStubCUESheet : CUESheet + { + private readonly IEnumerable ctdbMetadata; + + public CtdbStubCUESheet(CUEConfig config, IEnumerable ctdbMetadata) + : base(config) + { + this.ctdbMetadata = ctdbMetadata; + } + + protected override IEnumerable LookupCtdbMetadata(CTDBMetadataSearch metadataSearch) + { + return ctdbMetadata; + } + } + + private static IEnumerable CreateCtdbMetadata() + { + return new[] + { + new CTDBResponseMeta + { + source = "ctdb", + artist = "CTDB Artist", + album = "CTDB Album", + track = new[] + { + new CTDBResponseMetaTrack + { + name = "CTDB Track", + artist = "CTDB Artist" + } + } + } + }; + } + + private sealed class CapturedHttpRequest + { + public string Method { get; set; } + public string UserAgent { get; set; } + public string ContentType { get; set; } + public bool ExpectContinue { get; set; } + public string Body { get; set; } + } + + [TestMethod] + public void CUEConfigAdvancedDoesNotExposeFreedbSettings() + { + var freedbProperties = TypeDescriptor.GetProperties(new CUEConfigAdvanced()) + .Cast() + .Where(property => property.Category == "Freedb") + .Select(property => property.Name) + .ToList(); + + CollectionAssert.AreEqual(new string[0], freedbProperties); + } + + private sealed class FakeTransport : IAccurateRipMetaTransport + { + public string Url { get; private set; } + public string Body { get; private set; } + public IWebProxy Proxy { get; private set; } + public int PostCount { get; private set; } + public string Response { get; set; } + public Exception Exception { get; set; } + + public string Post(string url, string body, IWebProxy proxy, Action checkStop) + { + PostCount++; + Url = url; + Body = body; + Proxy = proxy; + + if (Exception != null) + throw Exception; + + return Response; + } + } + } +} diff --git a/CUETools/CUETools.TestProcessor/CUETools.TestProcessor.csproj b/CUETools/CUETools.TestProcessor/CUETools.TestProcessor.csproj index 058f176c5..f75041fa2 100644 --- a/CUETools/CUETools.TestProcessor/CUETools.TestProcessor.csproj +++ b/CUETools/CUETools.TestProcessor/CUETools.TestProcessor.csproj @@ -1,144 +1,53 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {A430AD28-B76A-4ED0-AF7D-D13B8969297F} - Library - CUETools.TestProcessor - CUETools.TestProcessor - 4 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - 3.5 - - - v4.7 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - true - full - false - ..\..\bin\$(Configuration)\net47\tests\ - DEBUG;TRACE - AllRules.ruleset - false - - - true - true - ..\..\bin\$(Configuration)\net47\tests\ - TRACE - pdbonly - AllRules.ruleset - false - - - - True - - - - - - - - - - - - - - - {5802C7E9-157E-4124-946D-70B5AE48A5A1} - CUETools.AccurateRip - False - - - {1DD41038-D885-46C5-8DDE-E0B82F066584} - CUETools.CDImage - False - - - {8A0426FA-0BC2-4C49-A6E5-1F9A68156F19} - CUETools.Codecs.LossyWAV - False - - - {6458A13A-30EF-45A9-9D58-E5031B17BEE2} - CUETools.Codecs - False - - - {14EE067E-C218-4625-9540-2361AB27C4A6} - CUETools.Compression - False - - - {c36efaa5-4f81-4a29-800d-ba866ff13bc8} - CUETools.CTDB.Types - - - {AA2A9A7E-45FB-4632-AD85-85B0E556F818} - CUETools.CTDB - False - - - {4911BD82-49EF-4858-8B51-5394F86739A4} - CUETools.Processor - False - - - {D2700165-3E77-4B28-928D-551F5FC11954} - CUETools.Ripper - False - - - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4} - Freedb - False - - - - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - \ No newline at end of file + + + + net10.0-windows + Library + CUETools.TestProcessor + CUETools.TestProcessor + true + false + false + true + $(NoWarn);WFO1000 + ..\..\bin\$(Configuration)\$(TargetFramework)\tests\ + false + $(BaseIntermediateOutputPath)\$(Configuration)\$(TargetFramework)\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CUETools/CUETools.TestProcessor/Fixtures/accuraterip-meta-amarok.json b/CUETools/CUETools.TestProcessor/Fixtures/accuraterip-meta-amarok.json new file mode 100644 index 000000000..4307b0359 --- /dev/null +++ b/CUETools/CUETools.TestProcessor/Fixtures/accuraterip-meta-amarok.json @@ -0,0 +1 @@ +{"_tocid":"858576","Artist":"Mike Oldfield","Album":"Amarok","Year":"1990","Genre":"Parlata","Label":"Virgin","Styles":"Progressive Electronic\rProg-Rock\rArt Rock","CatalogNum":"CDV 2640","UPC":"5012981264024","_albumid":"155298","_arturl":"http://meta.accuraterip.com/albumart/0000001F89DA34F26C1A","tracks":[{"TrackNumber":"1","Title":"Amarok","Artist":"Mike Oldfield","Composers":"","Conductors":"","ISRC":"GBAAA0000417"}]} \ No newline at end of file diff --git a/CUETools/CUETools.TestProcessor/ProcessorTest.cs b/CUETools/CUETools.TestProcessor/ProcessorTest.cs index 997c0a2bc..7179164fe 100644 --- a/CUETools/CUETools.TestProcessor/ProcessorTest.cs +++ b/CUETools/CUETools.TestProcessor/ProcessorTest.cs @@ -121,17 +121,27 @@ public void OpenEnhancedCD() ///A test for one-track CD /// [TestMethod()] - public void OpenOneTrackCD() - { - // test one-track CD - CUESheet target = new CUESheet(new CUEConfig()); - target.Open("Amarok\\Amarok.cue"); - Assert.AreEqual("00041f6d-00083ece-020e1201", AccurateRipVerify.CalculateAccurateRipId(target.TOC), "Wrong TOC"); - } - - [TestMethod()] - [Ignore] - public void CTDBResponseTest() + public void OpenOneTrackCD() + { + // test one-track CD + CUESheet target = new CUESheet(new CUEConfig()); + target.Open("Amarok\\Amarok.cue"); + Assert.AreEqual("00041f6d-00083ece-020e1201", AccurateRipVerify.CalculateAccurateRipId(target.TOC), "Wrong TOC"); + } + + [TestMethod()] + public void CTDBUserIdIsStable() + { + string first = CUEToolsDB.GetUUID(); + string second = CUEToolsDB.GetUUID(); + + Assert.IsFalse(string.IsNullOrEmpty(first)); + Assert.AreEqual(first, second); + } + + [TestMethod()] + [Ignore] + public void CTDBResponseTest() { XmlSerializer serializer = new XmlSerializer(typeof(CTDBResponse)); string respXml = File.ReadAllText("Z:\\ctdb.xml"); diff --git a/CUETools/CUETools.csproj b/CUETools/CUETools.csproj index ae92622fc..ade41ba78 100644 --- a/CUETools/CUETools.csproj +++ b/CUETools/CUETools.csproj @@ -1,430 +1,66 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {EF351583-A9CD-4530-92C3-20AC02136BC2} - WinExe - Properties - JDP - CUETools - cue2.ico - 611F49E043614FEDCFF1B128868CC8DC0B6656C5 - CUETools_TemporaryKey.pfx - false - false - true - LocalIntranet - - - 3.5 - - - v4.7 - - ftp://cuetools.net/cuetools.net/install/cuetools/ - true - Web - true - Background - 1 - Days - true - false - true - http://cuetools.net/install/cuetools/ - http://cuetools.net/ - CUETools - CUETools - true - publish.htm - 0 - 1.0.0.%2a - false - true - - - true - full - false - ..\bin\Debug\net47\ - DEBUG;TRACE - prompt - 4 - true - x86 - AllRules.ruleset - - - pdbonly - true - ..\bin\Release\net47\ - TRACE - prompt - 4 - true - AnyCPU - true - AllRules.ruleset - false - - - - - - - - - - - - - - - - - - - Form - - - frmSubmit.cs - - - Form - - - frmAbout.cs - - - Form - - - frmBatch.cs - - - Form - - - frmChoice.cs - - - Form - - - frmOverwrite.cs - - - Form - - - frmPassword.cs - - - Form - - - frmReport.cs - - - Form - - - frmSettings.cs - - - Form - - - frmCUETools.cs - - - - - frmAbout.cs - Designer - - - Designer - frmAbout.cs - - - Designer - frmAbout.cs - - - Designer - frmBatch.cs - - - frmChoice.cs - Designer - - - Designer - frmChoice.cs - - - frmChoice.cs - - - Designer - frmCUETools.cs - - - Designer - frmCUETools.cs - - - Designer - frmCUETools.cs - - - frmOverwrite.cs - - - frmPassword.cs - Designer - - - Designer - frmPassword.cs - - - frmPassword.cs - - - frmReport.cs - Designer - - - Designer - frmReport.cs - - - frmReport.cs - - - Designer - frmSettings.cs - - - Designer - frmSettings.cs - - - Designer - frmSettings.cs - - - frmSubmit.cs - - - frmSubmit.cs - - - frmSubmit.cs - - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - True - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - {CA4D64E6-6544-4A29-8BA5-7DB08D50D072} - CUEControls - - - {5802C7E9-157E-4124-946D-70B5AE48A5A1} - CUETools.AccurateRip - - - {1DD41038-D885-46C5-8DDE-E0B82F066584} - CUETools.CDImage - - - {6458A13A-30EF-45A9-9D58-E5031B17BEE2} - CUETools.Codecs - - - {14EE067E-C218-4625-9540-2361AB27C4A6} - CUETools.Compression - - - {c36efaa5-4f81-4a29-800d-ba866ff13bc8} - CUETools.CTDB.Types - - - {AA2A9A7E-45FB-4632-AD85-85B0E556F818} - CUETools.CTDB - - - {4911BD82-49EF-4858-8B51-5394F86739A4} - CUETools.Processor - - - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4} - Freedb - - - {1219a514-d3fa-40db-bbb2-92ce05e35839} - TaglibSharp - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 2.0 %28x86%29 - true - - - False - .NET Framework 3.0 %28x86%29 - false - - - False - .NET Framework 3.5 - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Visual C++ Runtime Libraries %28x86%29 - true - - - - - False - - - - - Include - True - Satellite - - - False - - - - - Include - True - Satellite - - - - - - - - \ No newline at end of file + + + + net10.0-windows + 2.2.6.0 + WinExe + CUETools + JDP + cue2.ico + Properties\app.manifest + 7.0 + true + false + true + $(NoWarn);WFO1000 + ..\bin\$(Configuration)\ + + + + x86 + + + + AnyCPU + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + diff --git a/CUETools/Program.cs b/CUETools/Program.cs index e8222a5be..894820db2 100644 --- a/CUETools/Program.cs +++ b/CUETools/Program.cs @@ -1,12 +1,11 @@ using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Runtime.Remoting; -using System.Runtime.Remoting.Channels.Ipc; -using System.Text; -using System.Threading; -using System.Windows.Forms; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Pipes; +using System.Text; +using System.Threading; +using System.Windows.Forms; using CUETools.Processor; using CUETools.Processor.Settings; @@ -74,12 +73,14 @@ static void Main(string[] args) } } - [Serializable] - class SingletonController : MarshalByRefObject - { - private static IpcChannel m_IPCChannel = null; - - public delegate bool ReceiveDelegate(string[] args); + class SingletonController + { + private static Mutex m_Mutex = null; + private static string m_PipeName = null; + private static Thread m_ListenerThread = null; + private static CancellationTokenSource m_Cancellation = null; + + public delegate bool ReceiveDelegate(string[] args); static private ReceiveDelegate m_Receive = null; static public ReceiveDelegate Receiver @@ -107,55 +108,143 @@ public static bool IamFirst(string id, ReceiveDelegate r) } } - public static bool IamFirst(string id) - { - try - { - m_IPCChannel = new IpcChannel(id); - } - catch - { - return false; - } - System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(m_IPCChannel, false); - RemotingConfiguration.RegisterWellKnownServiceType( - typeof(SingletonController), - "SingletonController", - WellKnownObjectMode.SingleCall); - return true; - } - - public static void Cleanup() - { - if (m_IPCChannel != null) - m_IPCChannel.StopListening(null); - m_IPCChannel = null; - } - - public static void Send(string id, string[] s) - { - SingletonController ctrl; - IpcChannel channel = new IpcChannel(); - System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(channel, false); - bool result = false; - try - { - ctrl = (SingletonController)Activator.GetObject(typeof(SingletonController), "ipc://" + id + "/SingletonController"); - result = ctrl.Receive(s); - } - catch - { - } + public static bool IamFirst(string id) + { + bool createdNew; + try + { + m_Mutex = new Mutex(true, "CUETools-" + id, out createdNew); + } + catch + { + return false; + } + + if (!createdNew) + { + m_Mutex.Dispose(); + m_Mutex = null; + return false; + } + + m_PipeName = "CUETools-" + id; + m_Cancellation = new CancellationTokenSource(); + m_ListenerThread = new Thread(Listen); + m_ListenerThread.IsBackground = true; + m_ListenerThread.Name = "CUETools single-instance listener"; + m_ListenerThread.Start(); + return true; + } + + public static void Cleanup() + { + CancellationTokenSource cancellation = m_Cancellation; + string pipeName = m_PipeName; + Thread listenerThread = m_ListenerThread; + + if (cancellation != null) + { + cancellation.Cancel(); + try + { + using (NamedPipeClientStream client = new NamedPipeClientStream(".", pipeName, PipeDirection.Out)) + client.Connect(50); + } + catch + { + } + } + + if (listenerThread != null) + listenerThread.Join(1000); + m_ListenerThread = null; + m_Cancellation = null; + m_PipeName = null; + + if (m_Mutex != null) + { + try + { + m_Mutex.ReleaseMutex(); + } + catch + { + } + m_Mutex.Dispose(); + } + m_Mutex = null; + } + + public static void Send(string id, string[] s) + { + bool result = false; + try + { + using (NamedPipeClientStream client = new NamedPipeClientStream(".", "CUETools-" + id, PipeDirection.InOut)) + { + client.Connect(1000); + BinaryWriter writer = new BinaryWriter(client, Encoding.UTF8, true); + writer.Write(s.Length); + foreach (string arg in s) + writer.Write(arg ?? ""); + writer.Flush(); + + BinaryReader reader = new BinaryReader(client, Encoding.UTF8, true); + result = reader.ReadBoolean(); + } + } + catch + { + } if (!result) MessageBox.Show("Another instance of the application seems to be running, but not responding.", - "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - public bool Receive(string[] s) - { - if (m_Receive == null) - return false; + "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + private static void Listen() + { + while (true) + { + CancellationTokenSource cancellation = m_Cancellation; + string pipeName = m_PipeName; + if (cancellation == null || cancellation.IsCancellationRequested || string.IsNullOrEmpty(pipeName)) + return; + + try + { + using (NamedPipeServerStream server = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1)) + { + server.WaitForConnection(); + if (cancellation.IsCancellationRequested) + continue; + + BinaryReader reader = new BinaryReader(server, Encoding.UTF8, true); + int count = reader.ReadInt32(); + string[] args = new string[count]; + for (int i = 0; i < count; i++) + args[i] = reader.ReadString(); + + bool result = Receive(args); + BinaryWriter writer = new BinaryWriter(server, Encoding.UTF8, true); + writer.Write(result); + writer.Flush(); + } + } + catch + { + cancellation = m_Cancellation; + if (cancellation == null || cancellation.IsCancellationRequested) + return; + Thread.Sleep(250); + } + } + } + + private static bool Receive(string[] s) + { + if (m_Receive == null) + return false; return m_Receive(s); } } -} \ No newline at end of file +} diff --git a/CUETools/Properties/Resources.Designer.cs b/CUETools/Properties/Resources.Designer.cs index a22d9de58..1d7fab57b 100644 --- a/CUETools/Properties/Resources.Designer.cs +++ b/CUETools/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace JDP.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/CUETools/Properties/Settings.Designer.cs b/CUETools/Properties/Settings.Designer.cs index e04f3d25c..1ee1fa702 100644 --- a/CUETools/Properties/Settings.Designer.cs +++ b/CUETools/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace JDP.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "18.6.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/CUETools/TestRipper/CDDriveReaderTest.cs b/CUETools/TestRipper/CDDriveReaderTest.cs index da7dc2227..8484f52e5 100644 --- a/CUETools/TestRipper/CDDriveReaderTest.cs +++ b/CUETools/TestRipper/CDDriveReaderTest.cs @@ -37,10 +37,11 @@ public TestContext TestContext } } - const int max_pass = 64; - const int Sectors2Read = 2400; - - bool markErrors = true; + const int max_pass = 64; + const int Sectors2Read = 2400; + const string FixtureDirectory = @"Y:\Temp\dbg\960"; + + bool markErrors = true; int _currentStart = 0, _realErrors = 0; byte[] _currentData = new byte[Sectors2Read * 4 * 588]; @@ -54,11 +55,13 @@ public TestContext TestContext //You can use the following additional attributes as you write your tests: // //Use ClassInitialize to run code before running the first test in the class - [ClassInitialize()] - public static void MyClassInitialize(TestContext testContext) - { - for (ulong i = 0; i < 256; i++) - { + [ClassInitialize()] + public static void MyClassInitialize(TestContext testContext) + { + EnsureExternalFixtureAvailable(); + + for (ulong i = 0; i < 256; i++) + { ulong bl = 0; for (int b = 0; b < 8; b++) bl += ((i >> b) & 1) << (b << 3); @@ -68,12 +71,12 @@ public static void MyClassInitialize(TestContext testContext) //Random rnd = new Random(2314); //rnd.NextBytes(_realData); - byte [] c2data = new byte[Sectors2Read * 296]; - for (int p = 0; p < max_pass; p++) - { - // string nm_d = string.Format("Y:\\Temp\\dbg\\{0:x}-{1:00}.bin", _currentStart, dbg_pass); - using (FileStream fs = new FileStream(string.Format("Y:\\Temp\\dbg\\960\\960-{0:00}.bin", p), FileMode.Open)) - using (FileStream fs2 = new FileStream(string.Format("Y:\\Temp\\dbg\\960\\960-{0:00}.c2", p), FileMode.Open)) + byte [] c2data = new byte[Sectors2Read * 296]; + for (int p = 0; p < max_pass; p++) + { + // string nm_d = string.Format("Y:\\Temp\\dbg\\{0:x}-{1:00}.bin", _currentStart, dbg_pass); + using (FileStream fs = new FileStream(Path.Combine(FixtureDirectory, string.Format("960-{0:00}.bin", p)), FileMode.Open)) + using (FileStream fs2 = new FileStream(Path.Combine(FixtureDirectory, string.Format("960-{0:00}.c2", p)), FileMode.Open)) { fs.Read(_realData, 0, Sectors2Read * 4 * 588); fs2.Read(c2data, 0, Sectors2Read * 296); @@ -107,10 +110,27 @@ public static void MyClassInitialize(TestContext testContext) // } // } } - using (FileStream fs = new FileStream(string.Format("Y:\\Temp\\dbg\\960\\960.bin", 0), FileMode.Open)) - fs.Read(_realData, 0, Sectors2Read * 4 * 588); - } - // + using (FileStream fs = new FileStream(Path.Combine(FixtureDirectory, "960.bin"), FileMode.Open)) + fs.Read(_realData, 0, Sectors2Read * 4 * 588); + } + + static void EnsureExternalFixtureAvailable() + { + if (!Directory.Exists(FixtureDirectory)) + Assert.Inconclusive("External TestRipper fixture directory is missing: " + FixtureDirectory); + + for (int p = 0; p < max_pass; p++) + { + string binPath = Path.Combine(FixtureDirectory, string.Format("960-{0:00}.bin", p)); + string c2Path = Path.Combine(FixtureDirectory, string.Format("960-{0:00}.c2", p)); + if (!File.Exists(binPath) || !File.Exists(c2Path)) + Assert.Inconclusive("External TestRipper fixture files are missing under: " + FixtureDirectory); + } + + if (!File.Exists(Path.Combine(FixtureDirectory, "960.bin"))) + Assert.Inconclusive("External TestRipper reference file is missing: " + Path.Combine(FixtureDirectory, "960.bin")); + } + // //Use ClassCleanup to run code after all tests in a class have run //[ClassCleanup()] //public static void MyClassCleanup() diff --git a/CUETools/TestRipper/TestRipper.csproj b/CUETools/TestRipper/TestRipper.csproj index dab36e2ce..c1ed0b84b 100644 --- a/CUETools/TestRipper/TestRipper.csproj +++ b/CUETools/TestRipper/TestRipper.csproj @@ -1,133 +1,36 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {5C8B61C0-BC3D-4316-B8A7-419D55BB5796} - Library - Properties - TestRipper - TestRipper - v4.7 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - 3.5 - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - true - full - false - ..\..\bin\$(Configuration)\net47\tests\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - ..\..\bin\$(Configuration)\net47\tests\ - TRACE - prompt - 4 - AllRules.ruleset - - - - - - - - - - - - - - {F2DFEB00-BB35-4665-85EA-CB8C7729A6B7} - Bwg.Logging - false - - - {A05B6AA6-0EC3-495D-BCC4-ECE1210071A8} - Bwg.Scsi - false - - - {1DD41038-D885-46C5-8DDE-E0B82F066584} - CUETools.CDImage - false - - - {6458A13A-30EF-45A9-9D58-E5031B17BEE2} - CUETools.Codecs - false - - - {8CF07381-BEA2-4AFC-B3DD-9B2F21C65A3A} - CUETools.Ripper.SCSI - false - - - {D2700165-3E77-4B28-928D-551F5FC11954} - CUETools.Ripper - false - - - - - - - - - - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - \ No newline at end of file + + + + net10.0-windows + Library + TestRipper + TestRipper + false + false + true + ..\..\bin\$(Configuration)\$(TargetFramework)\tests\ + false + $(BaseIntermediateOutputPath)\$(Configuration)\$(TargetFramework)\ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CUETools/TestRipper/app.config b/CUETools/TestRipper/app.config index 73859b00e..8a046a0b8 100644 --- a/CUETools/TestRipper/app.config +++ b/CUETools/TestRipper/app.config @@ -1,3 +1,3 @@ - + diff --git a/CUETools/app.config b/CUETools/app.config index 7680fe265..a19e70143 100644 --- a/CUETools/app.config +++ b/CUETools/app.config @@ -6,6 +6,6 @@ - + diff --git a/CUETools/frmCUETools.cs b/CUETools/frmCUETools.cs index 58218f689..dfea56208 100644 --- a/CUETools/frmCUETools.cs +++ b/CUETools/frmCUETools.cs @@ -1000,12 +1000,12 @@ private void WriteAudioFilesThread(object o) cueSheet.CopyMetadata(recentEntry.Metadata); } - UpdateOutputPath(pathIn, cueSheet); - pathOut = txtOutputPath.Text; - if (dlgRes != DialogResult.Cancel && cueSheet.AlbumArt.Count != 0) - pictureBoxMotd.Image = cueSheet.Cover; - else - pictureBoxMotd.Image = motdImage; + UpdateOutputPath(pathIn, cueSheet); + pathOut = txtOutputPath.Text; + if (dlgRes != DialogResult.Cancel && cueSheet.AlbumArt.Count != 0) + SetMotdImage(GetCoverImage(cueSheet)); + else + SetMotdImage(motdImage); }); if (dlgRes == DialogResult.Cancel) @@ -1047,10 +1047,10 @@ private void WriteAudioFilesThread(object o) } if (outputExists.Count == 0) { - this.Invoke((MethodInvoker)delegate () - { - pictureBoxMotd.Image = cueSheet.Cover ?? motdImage; - }); + this.Invoke((MethodInvoker)delegate () + { + SetMotdImage(GetCoverImage(cueSheet) ?? motdImage); + }); cueSheet.UsePregapForFirstTrackInSingleFile = _usePregapForFirstTrackInSingleFile && !outputAudio; if (script == null || script.name == "default") { @@ -1297,12 +1297,12 @@ private void SetupControls(bool running) textBatchReport.Text = newText; } - if (!running) - { - UpdateActions(); - pictureBoxMotd.Image = motdImage; - toolStripStatusLabelProcessed.Text = ""; - } + if (!running) + { + UpdateActions(); + SetMotdImage(motdImage); + toolStripStatusLabelProcessed.Text = ""; + } //rbGapsLeftOut.Visible = // rbGapsPrepended.Visible = @@ -2108,10 +2108,10 @@ private void rbAction_CheckedChanged(object sender, EventArgs e) SetupControls(false); } - public bool OnSecondCall(string[] args) - { - if ((_workThread != null) && (_workThread.IsAlive)) - return false; + public bool OnSecondCall(string[] args) + { + if ((_workThread != null) && (_workThread.IsAlive)) + return false; this.Invoke((MethodInvoker)delegate() { if (args.Length == 1) @@ -2152,11 +2152,24 @@ public bool OnSecondCall(string[] args) WindowState = FormWindowState.Normal; Activate(); }); - return true; - } - - private void locateInExplorerToolStripMenuItem_Click(object sender, EventArgs e) - { + return true; + } + + private void SetMotdImage(Image image) + { + Image previous = pictureBoxMotd.Image; + pictureBoxMotd.Image = image; + if (previous != null && !object.ReferenceEquals(previous, motdImage) && !object.ReferenceEquals(previous, image)) + previous.Dispose(); + } + + private static Image GetCoverImage(CUESheet cueSheet) + { + return cueSheet?.Cover; + } + + private void locateInExplorerToolStripMenuItem_Click(object sender, EventArgs e) + { var node = contextMenuStripFileTree.Tag as CUEControls.FileSystemTreeNode; if (node is FileSystemTreeNodeLocalDBFolder) { diff --git a/CUETools/frmChoice.cs b/CUETools/frmChoice.cs index e2007424d..3a1e84340 100644 --- a/CUETools/frmChoice.cs +++ b/CUETools/frmChoice.cs @@ -14,10 +14,12 @@ namespace JDP { public partial class frmChoice : Form { - public frmChoice() - { - InitializeComponent(); - } + public frmChoice() + { + InitializeComponent(); + if (!imageList1.Images.ContainsKey(AccurateRipMeta.SourceKey)) + imageList1.Images.Add(AccurateRipMeta.SourceKey, Properties.Resources.accuraterip16); + } public CUESheet CUE; public CUEConfig config; @@ -70,20 +72,23 @@ private void AddItem(object i) item.Tag = entry; listChoices.Items.Add(item); - if (entry.ImageKey == "freedb") - { - // check if the entry contains non-iso characters, - // and add a second one if it does +#if DORMANT_FREEDB + // FreeDB encoding repair is dormant. AccurateRip Meta entries are already Unicode JSON. + if (entry.ImageKey == "freedb") + { + // check if the entry contains non-iso characters, + // and add a second one if it does CUEMetadata copy = new CUEMetadata(entry.metadata); if (copy.FreedbToEncoding()) { entry = new CUEMetadataEntry(copy, entry.TOC, entry.ImageKey); item = new ListViewItem(entry.ToString(), entry.ImageKey); item.Tag = entry; - listChoices.Items.Add(item); - } - } - } + listChoices.Items.Add(item); + } + } +#endif + } else { ListViewItem item = new ListViewItem(i.ToString(), -1); diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..80875d00b --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,6 @@ + + + + $(DefineConstants);DORMANT_FREEDB + + diff --git a/Freedb/Freedb.csproj b/Freedb/Freedb.csproj index 91ef4fac7..6d9da7e5e 100644 --- a/Freedb/Freedb.csproj +++ b/Freedb/Freedb.csproj @@ -1,167 +1,12 @@ - - - - Local - 9.0.30729 - 2.0 - {5ADCFD6D-BFEA-4B10-BB45-9083BBB56AF4} - Debug - AnyCPU - - - - - Freedb - - - JScript - Grid - IE50 - false - Library - Freedb - OnBuildSuccess - - - - - - - 3.5 - v4.7 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - ..\bin\Debug\net47\ - false - 285212672 - false - - - DEBUG;TRACE - - - true - 4096 - false - - - false - false - false - false - 4 - full - prompt - AllRules.ruleset - - - ..\bin\Release\net47\ - false - 285212672 - false - - - - - - - true - 4096 - false - - - true - false - false - false - 4 - pdbonly - prompt - AllRules.ruleset - - - - System - - - System.Data - - - System.Drawing - - - System.Windows.Forms - - - System.XML - - - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - - - - - - \ No newline at end of file + + + + netstandard2.1 + Freedb + Freedb + Library + false + ..\bin\$(Configuration)\ + + + diff --git a/MusicBrainz/MusicBrainz.csproj b/MusicBrainz/MusicBrainz.csproj index 9698612b5..81e49830d 100644 --- a/MusicBrainz/MusicBrainz.csproj +++ b/MusicBrainz/MusicBrainz.csproj @@ -1,81 +1,10 @@ - - - - Debug - AnyCPU - 9.0.30729 - {74C2036B-2C9B-4FC8-B7BD-AE81A8DCE533} - Library - MusicBrainz - MusicBrainz - - - - - 2.0 - - - true - full - false - ..\bin\Debug\net47\ - prompt - 4 - . - true - false - v4.7 - true - - - pdbonly - true - ..\bin\Release\net47\ - prompt - 4 - . - true - v1.1 - AnyCPU - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + netstandard2.1 + MusicBrainz + MusicBrainz + ..\bin\$(Configuration)\ + + + diff --git a/ProgressODoom/ProgressODoom.csproj b/ProgressODoom/ProgressODoom.csproj index 50f560b14..e7a0f2bfb 100644 --- a/ProgressODoom/ProgressODoom.csproj +++ b/ProgressODoom/ProgressODoom.csproj @@ -1,228 +1,20 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {8DD1E84B-0B03-4C0B-9B42-1E49F75E7CB1} - Library - Properties - ProgressODoom - ProgressODoom - - - 3.5 - - - false - v4.7 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - true - full - false - ..\bin\Debug\net47\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - ..\bin\Release\net47\ - TRACE - prompt - 4 - AllRules.ruleset - - - - - - - - - - - Component - - - Component - - - Component - - - Component - - - Component - - - Component - - - Component - - - Component - - - - Component - - - Component - - - Component - - - Component - - - Component - - - Component - - - - - - - - - Component - - - Component - - - Component - - - Component - - - Component - - - Component - - - Component - - - - Component - - - Component - - - Component - - - Component - - - Component - - - Component - - - Component - - - Component - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 2.0 %28x86%29 - true - - - False - .NET Framework 3.0 %28x86%29 - false - - - False - .NET Framework 3.5 - false - - - False - .NET Framework 3.5 SP1 - false - - - - - \ No newline at end of file + + + + net10.0-windows + Library + ProgressODoom + ProgressODoom + 7.0 + true + false + false + $(NoWarn);WFO1000 + ..\bin\$(Configuration)\ + + + + + + + diff --git a/README.md b/README.md index 0e53ea8a4..f50cf7da5 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,10 @@ Prebuilt binaries can be downloaded from [CUETools Download](http://cue.tools/wi `git apply --directory=ThirdParty/MAC_SDK ThirdParty/ThirdParty_MAC_SDK_CUETools.patch` `git apply --directory=ThirdParty/taglib-sharp ThirdParty/submodule_taglib-sharp_CUETools.patch` `git apply --directory=ThirdParty/WavPack ThirdParty/submodule_WavPack_CUETools.patch` +`git apply --directory=ThirdParty/openclnet ThirdParty/submodule_openclnet_CUETools.patch` `git apply --directory=ThirdParty/WindowsMediaLib ThirdParty/submodule_WindowsMediaLib_CUETools.patch` * The solution can be built using Microsoft Visual Studio 2017 or newer (Community Edition will work) - * Install the required .NET development tools (currently .NET Framework 4.7 and .NET Core 2.0) + * Install the required .NET development tools (currently .NET 10 SDK) * Install an appropriate Windows SDK version (e.g. 10.0.16299.0 or newer) * Install the Microsoft Visual Studio Installer Projects * Open cuetools.net\CUETools.sln diff --git a/ThirdParty/WavPack b/ThirdParty/WavPack index 4827b9889..5803634a0 160000 --- a/ThirdParty/WavPack +++ b/ThirdParty/WavPack @@ -1 +1 @@ -Subproject commit 4827b9889665b937b6ed71b9c6c0123152cd7a02 +Subproject commit 5803634a030e2a11dba602ba057b89cc34486c67 diff --git a/ThirdParty/submodule_WindowsMediaLib_CUETools.patch b/ThirdParty/submodule_WindowsMediaLib_CUETools.patch index 5b868f610..aafaacf66 100644 --- a/ThirdParty/submodule_WindowsMediaLib_CUETools.patch +++ b/ThirdParty/submodule_WindowsMediaLib_CUETools.patch @@ -1,7 +1,16 @@ diff --git a/wmlib/src/WindowsMediaLib.csproj b/wmlib/src/WindowsMediaLib.csproj -index 24c8569..62cb8db 100644 +index 24c8569..d908906 100644 --- a/wmlib/src/WindowsMediaLib.csproj +++ b/wmlib/src/WindowsMediaLib.csproj +@@ -1,7 +1,7 @@ +  + + +- net47;net20 ++ netstandard2.1 + 1.1.0.0 + WindowsMediaLib + WindowsMediaLib @@ -10,7 +10,9 @@ Copyright (c) 2008-2018 snarfle snarfle diff --git a/ThirdParty/submodule_openclnet_CUETools.patch b/ThirdParty/submodule_openclnet_CUETools.patch new file mode 100644 index 000000000..c0bd63145 --- /dev/null +++ b/ThirdParty/submodule_openclnet_CUETools.patch @@ -0,0 +1,21 @@ +diff --git a/source/OpenCLNet.csproj b/source/OpenCLNet.csproj +index f2ec492..d8a2bce 100644 +--- a/source/OpenCLNet.csproj ++++ b/source/OpenCLNet.csproj +@@ -1,7 +1,7 @@ +  + + +- net47 ++ net10.0-windows + 0.5.1.0 + OpenCL.Net + OpenCL.Net +@@ -11,6 +11,7 @@ + Olav Kalgraf + OpenCLNet + OpenCLNet ++ 7.0 + + + diff --git a/ThirdParty/submodule_taglib-sharp_CUETools.patch b/ThirdParty/submodule_taglib-sharp_CUETools.patch index 13c96332a..542797d26 100644 --- a/ThirdParty/submodule_taglib-sharp_CUETools.patch +++ b/ThirdParty/submodule_taglib-sharp_CUETools.patch @@ -1,8 +1,78 @@ +diff --git a/Directory.Build.props b/Directory.Build.props +index dec1261..8f041a1 100644 +--- a/Directory.Build.props ++++ b/Directory.Build.props +@@ -6,15 +6,11 @@ + git + + +- net462;netcoreapp3.1 +- net462;netstandard2.0 ++ net10.0 ++ netstandard2.1 + latest + + +- +- full +- +- + + + +diff --git a/src/TaglibSharp.Tests/Images/Validators/ImageTest.cs b/src/TaglibSharp.Tests/Images/Validators/ImageTest.cs +index dfbd151..65f6d9d 100644 +--- a/src/TaglibSharp.Tests/Images/Validators/ImageTest.cs ++++ b/src/TaglibSharp.Tests/Images/Validators/ImageTest.cs +@@ -5,6 +5,7 @@ + + using SixLabors.ImageSharp; + using SixLabors.ImageSharp.Advanced; ++using SixLabors.ImageSharp.PixelFormats; + + using TagLib; + +@@ -146,9 +147,9 @@ string ReadImageData (TagLib.Image.File file) + // TODO, ImageSharp doesn't support tiff yet (4/25/2020): https://github.com/SixLabors/ImageSharp/issues/12 + md5Sum = "";// Utils.Md5Encode (v.Data); + } else { +- using var image = Image.Load (v.Data); +- image.TryGetSinglePixelSpan (out var span); +- byte[] result = MemoryMarshal.AsBytes (span).ToArray (); ++ using var image = Image.Load (v.Data); ++ image.DangerousTryGetSinglePixelMemory (out var memory); ++ byte[] result = MemoryMarshal.AsBytes (memory.Span).ToArray (); + md5Sum = Utils.Md5Encode (result); + } + +diff --git a/src/TaglibSharp.Tests/TaglibSharp.Tests.csproj b/src/TaglibSharp.Tests/TaglibSharp.Tests.csproj +index 5e1466b..09061a8 100644 +--- a/src/TaglibSharp.Tests/TaglibSharp.Tests.csproj ++++ b/src/TaglibSharp.Tests/TaglibSharp.Tests.csproj +@@ -12,13 +12,13 @@ + + + +- +- +- ++ ++ ++ + + +- +- ++ ++ + + + diff --git a/src/TaglibSharp/Aac/AudioHeader.cs b/src/TaglibSharp/Aac/AudioHeader.cs index 50613aa..8828d19 100644 --- a/src/TaglibSharp/Aac/AudioHeader.cs +++ b/src/TaglibSharp/Aac/AudioHeader.cs -@@ -203,6 +203,18 @@ public class AudioHeader : IAudioCodec +@@ -203,6 +203,18 @@ public int AudioBitrate { } } @@ -25,7 +95,7 @@ diff --git a/src/TaglibSharp/Aiff/StreamHeader.cs b/src/TaglibSharp/Aiff/StreamH index 318be17..8ad1ec4 100644 --- a/src/TaglibSharp/Aiff/StreamHeader.cs +++ b/src/TaglibSharp/Aiff/StreamHeader.cs -@@ -205,6 +205,18 @@ public StreamHeader (ByteVector data, long streamLength) +@@ -205,6 +205,18 @@ public int AudioBitrate { } } @@ -48,7 +118,7 @@ diff --git a/src/TaglibSharp/Ape/StreamHeader.cs b/src/TaglibSharp/Ape/StreamHea index 75a2675..4bdd5c3 100644 --- a/src/TaglibSharp/Ape/StreamHeader.cs +++ b/src/TaglibSharp/Ape/StreamHeader.cs -@@ -294,6 +294,18 @@ public StreamHeader (ByteVector data, long streamLength) +@@ -294,6 +294,18 @@ public int AudioBitrate { } } @@ -67,7 +137,7 @@ index 75a2675..4bdd5c3 100644 /// /// Gets the sample rate of the audio represented by the /// current instance. -@@ -356,4 +368,5 @@ public StreamHeader (ByteVector data, long streamLength) +@@ -356,4 +368,5 @@ public int BitsPerSample { public CompressionLevel Compression { get; private set; } #endregion @@ -80,7 +150,7 @@ diff --git a/src/TaglibSharp/Ape/Tag.cs b/src/TaglibSharp/Ape/Tag.cs index c3b631b..49a7f37 100644 --- a/src/TaglibSharp/Ape/Tag.cs +++ b/src/TaglibSharp/Ape/Tag.cs -@@ -1536,6 +1536,74 @@ IEnumerator IEnumerable.GetEnumerator () +@@ -1536,6 +1536,74 @@ public override string MusicBrainzReleaseCountry { set { SetValue ("RELEASECOUNTRY", value); } } @@ -159,7 +229,7 @@ diff --git a/src/TaglibSharp/Asf/FilePropertiesObject.cs b/src/TaglibSharp/Asf/F index fc0f513..64111bd 100644 --- a/src/TaglibSharp/Asf/FilePropertiesObject.cs +++ b/src/TaglibSharp/Asf/FilePropertiesObject.cs -@@ -198,6 +198,12 @@ public FilePropertiesObject (File file, long position) +@@ -198,6 +198,12 @@ public TimeSpan SendDuration { /// public ulong Preroll { get; private set; } @@ -176,7 +246,7 @@ diff --git a/src/TaglibSharp/Asf/Tag.cs b/src/TaglibSharp/Asf/Tag.cs index 84717ba..5251b84 100644 --- a/src/TaglibSharp/Asf/Tag.cs +++ b/src/TaglibSharp/Asf/Tag.cs -@@ -1298,6 +1298,40 @@ IEnumerator IEnumerable.GetEnumerator () +@@ -1298,6 +1298,40 @@ public override string MusicBrainzReleaseCountry { set { SetDescriptorString (value, "MusicBrainz/Album Release Country"); } } @@ -221,7 +291,7 @@ diff --git a/src/TaglibSharp/CombinedTag.cs b/src/TaglibSharp/CombinedTag.cs index 009eb8f..ee59e6e 100644 --- a/src/TaglibSharp/CombinedTag.cs +++ b/src/TaglibSharp/CombinedTag.cs -@@ -1637,6 +1637,162 @@ protected void ClearTags () +@@ -1637,6 +1637,162 @@ public override string MusicBrainzReleaseCountry { } } @@ -388,7 +458,7 @@ diff --git a/src/TaglibSharp/Dsf/StreamHeader.cs b/src/TaglibSharp/Dsf/StreamHea index 339d2f3..0651c84 100644 --- a/src/TaglibSharp/Dsf/StreamHeader.cs +++ b/src/TaglibSharp/Dsf/StreamHeader.cs -@@ -249,6 +249,18 @@ public StreamHeader (ByteVector data, long streamLength) +@@ -249,6 +249,18 @@ public int AudioBitrate { } } @@ -423,7 +493,7 @@ index ecc4955..1bfb20a 100644 IsLastBlock = false; BlockSize = blockSize; diff --git a/src/TaglibSharp/Flac/Picture.cs b/src/TaglibSharp/Flac/Picture.cs -index 8b6659f..4685ad5 100644 +index 8b6659f..3a41783 100644 --- a/src/TaglibSharp/Flac/Picture.cs +++ b/src/TaglibSharp/Flac/Picture.cs @@ -22,6 +22,7 @@ @@ -440,7 +510,7 @@ index 8b6659f..4685ad5 100644 - if (!(picture is Picture flac_picture)) + if (!(picture is Picture flac_picture)) { -+#if NET47 || NET20 ++#if NET48 || NET20 + try + { + using (System.Drawing.Image ipicture_picture = (Bitmap)((new ImageConverter ()).ConvertFrom (picture.Data.Data))) @@ -464,7 +534,7 @@ diff --git a/src/TaglibSharp/Flac/StreamHeader.cs b/src/TaglibSharp/Flac/StreamH index 5a57bbc..0c4d054 100644 --- a/src/TaglibSharp/Flac/StreamHeader.cs +++ b/src/TaglibSharp/Flac/StreamHeader.cs -@@ -130,6 +130,18 @@ public StreamHeader (ByteVector data, long streamLength) +@@ -130,6 +130,18 @@ public int AudioBitrate { } } @@ -537,7 +607,7 @@ diff --git a/src/TaglibSharp/Id3v2/Tag.cs b/src/TaglibSharp/Id3v2/Tag.cs index 1c3676f..c780d76 100644 --- a/src/TaglibSharp/Id3v2/Tag.cs +++ b/src/TaglibSharp/Id3v2/Tag.cs -@@ -2083,6 +2083,77 @@ IEnumerator IEnumerable.GetEnumerator () +@@ -2083,6 +2083,77 @@ public override string MusicBrainzReleaseCountry { set { SetUserTextAsString ("MusicBrainz Album Release Country", value); } } @@ -619,7 +689,7 @@ diff --git a/src/TaglibSharp/Matroska/AudioTrack.cs b/src/TaglibSharp/Matroska/A index e3ee49e..062f5cf 100644 --- a/src/TaglibSharp/Matroska/AudioTrack.cs +++ b/src/TaglibSharp/Matroska/AudioTrack.cs -@@ -127,6 +127,18 @@ public AudioTrack (File _file, EBMLreader element) +@@ -127,6 +127,18 @@ public int AudioBitrate { get { return 0; } } @@ -642,7 +712,7 @@ diff --git a/src/TaglibSharp/Matroska/Tag.cs b/src/TaglibSharp/Matroska/Tag.cs index 0fc3f7d..bc139ee 100644 --- a/src/TaglibSharp/Matroska/Tag.cs +++ b/src/TaglibSharp/Matroska/Tag.cs -@@ -1248,6 +1248,34 @@ uint GetUint (string key, string subkey = null, bool recu = false) +@@ -1248,6 +1248,34 @@ public override string MusicBrainzReleaseCountry { set { } } @@ -681,7 +751,7 @@ diff --git a/src/TaglibSharp/Mpc/StreamHeader.cs b/src/TaglibSharp/Mpc/StreamHea index 16bb005..987114c 100644 --- a/src/TaglibSharp/Mpc/StreamHeader.cs +++ b/src/TaglibSharp/Mpc/StreamHeader.cs -@@ -361,6 +361,18 @@ ulong ReadSize (ByteVector data, ref int pos) +@@ -361,6 +361,18 @@ public int AudioBitrate { } } @@ -704,7 +774,7 @@ diff --git a/src/TaglibSharp/Mpeg/AudioHeader.cs b/src/TaglibSharp/Mpeg/AudioHea index d18ea77..06a15e1 100644 --- a/src/TaglibSharp/Mpeg/AudioHeader.cs +++ b/src/TaglibSharp/Mpeg/AudioHeader.cs -@@ -356,6 +356,18 @@ public struct AudioHeader : IAudioCodec +@@ -356,6 +356,18 @@ public int AudioBitrate { } } @@ -762,7 +832,7 @@ index 020aab1..ac53ef5 100644 continue; } else { return (AppleDataBox)box.GetChild (BoxType.Data); -@@ -1580,6 +1585,77 @@ IEnumerator IEnumerable.GetEnumerator () +@@ -1580,6 +1585,77 @@ public override string MusicBrainzReleaseCountry { set { SetDashBox ("com.apple.iTunes", "MusicBrainz Album Release Country", value); } } @@ -840,7 +910,7 @@ index 020aab1..ac53ef5 100644 /// /// Gets and sets the ReplayGain Track Value of the media represented by /// the current instance. -@@ -1742,21 +1818,21 @@ IEnumerator IEnumerable.GetEnumerator () +@@ -1742,21 +1818,21 @@ public override string ISRC { set { SetDashBox ("com.apple.iTunes", "ISRC", value); } } @@ -877,24 +947,11 @@ index 020aab1..ac53ef5 100644 /// /// Gets and sets the Remixer -diff --git a/src/TaglibSharp/Mpeg4/BoxFactory.cs b/src/TaglibSharp/Mpeg4/BoxFactory.cs -index 1c952f6..0fed9f4 100644 ---- a/src/TaglibSharp/Mpeg4/BoxFactory.cs -+++ b/src/TaglibSharp/Mpeg4/BoxFactory.cs -@@ -112,7 +112,7 @@ public static class BoxFactory - return new AppleElementaryStreamDescriptor (header, file, handler); - else if (type == BoxType.Free || type == BoxType.Skip) - return new IsoFreeSpaceBox (header, file, handler); -- else if (type == BoxType.Mean || type == BoxType.Name) -+ else if ((type == BoxType.Mean || type == BoxType.Name) && header.DataSize >= 4) - return new AppleAdditionalInfoBox (header, file, handler); - - // If we still don't have a tag, and we're inside an diff --git a/src/TaglibSharp/Mpeg4/Boxes/IsoAudioSampleEntry.cs b/src/TaglibSharp/Mpeg4/Boxes/IsoAudioSampleEntry.cs index d8f71c3..6f59f99 100644 --- a/src/TaglibSharp/Mpeg4/Boxes/IsoAudioSampleEntry.cs +++ b/src/TaglibSharp/Mpeg4/Boxes/IsoAudioSampleEntry.cs -@@ -189,6 +189,18 @@ public IsoAudioSampleEntry (BoxHeader header, TagLib.File file, IsoHandlerBox ha +@@ -189,6 +189,18 @@ public int AudioBitrate { } } @@ -917,7 +974,7 @@ diff --git a/src/TaglibSharp/Ogg/Codecs/Opus.cs b/src/TaglibSharp/Ogg/Codecs/Opu index a4717cc..68f2219 100644 --- a/src/TaglibSharp/Ogg/Codecs/Opus.cs +++ b/src/TaglibSharp/Ogg/Codecs/Opus.cs -@@ -209,6 +209,18 @@ public override void SetCommentPacket (ByteVectorCollection packets, XiphComment +@@ -209,6 +209,18 @@ public int AudioBitrate { get { return 0; } } @@ -952,7 +1009,7 @@ index 917f9cb..c6c8438 100644 else packets.Insert (1, data); } -@@ -203,6 +207,18 @@ public override void SetCommentPacket (ByteVectorCollection packets, XiphComment +@@ -203,6 +207,18 @@ public int AudioBitrate { } } @@ -975,7 +1032,7 @@ diff --git a/src/TaglibSharp/Ogg/GroupedComment.cs b/src/TaglibSharp/Ogg/Grouped index 80f2a90..2b30997 100644 --- a/src/TaglibSharp/Ogg/GroupedComment.cs +++ b/src/TaglibSharp/Ogg/GroupedComment.cs -@@ -1290,6 +1290,138 @@ public void AddComment (uint streamSerialNumber, ByteVector data) +@@ -1290,6 +1290,138 @@ public override string MusicBrainzReleaseCountry { set { if (tags.Count > 0) tags[0].MusicBrainzReleaseCountry = value; } } @@ -1118,7 +1175,7 @@ diff --git a/src/TaglibSharp/Ogg/XiphComment.cs b/src/TaglibSharp/Ogg/XiphCommen index dd1f98e..102c200 100644 --- a/src/TaglibSharp/Ogg/XiphComment.cs +++ b/src/TaglibSharp/Ogg/XiphComment.cs -@@ -982,6 +982,10 @@ IEnumerator IEnumerable.GetEnumerator () +@@ -982,6 +982,10 @@ public override uint TrackCount { null && uint.TryParse (text, out var value)) return value; @@ -1129,7 +1186,7 @@ index dd1f98e..102c200 100644 if ((text = GetFirstField ("TRACKNUMBER")) != null && (values = text.Split ('/')) .Length > 1 && uint.TryParse ( -@@ -1047,6 +1051,10 @@ IEnumerator IEnumerable.GetEnumerator () +@@ -1047,6 +1051,10 @@ public override uint DiscCount { && uint.TryParse (text, out var value)) return value; @@ -1140,7 +1197,7 @@ index dd1f98e..102c200 100644 if ((text = GetFirstField ("DISCNUMBER")) != null && (values = text.Split ('/')) .Length > 1 && uint.TryParse ( -@@ -1400,6 +1408,74 @@ IEnumerator IEnumerable.GetEnumerator () +@@ -1400,6 +1408,74 @@ public override string MusicBrainzReleaseCountry { set { SetField ("RELEASECOUNTRY", value); } } @@ -1215,7 +1272,7 @@ index dd1f98e..102c200 100644 /// /// Gets and sets a collection of pictures associated with /// the media represented by the current instance. -@@ -1637,19 +1713,19 @@ IEnumerator IEnumerable.GetEnumerator () +@@ -1637,19 +1713,19 @@ public override string RemixedBy { set { SetField ("REMIXEDBY", value); } } @@ -1252,7 +1309,7 @@ diff --git a/src/TaglibSharp/Properties.cs b/src/TaglibSharp/Properties.cs index befda88..5a6ce9a 100644 --- a/src/TaglibSharp/Properties.cs +++ b/src/TaglibSharp/Properties.cs -@@ -242,6 +242,32 @@ public Properties (TimeSpan duration, IEnumerable codecs) +@@ -242,6 +242,32 @@ public int AudioBitrate { } } @@ -1289,7 +1346,7 @@ diff --git a/src/TaglibSharp/Riff/WaveFormatEx.cs b/src/TaglibSharp/Riff/WaveFor index 360a2b6..03b2873 100644 --- a/src/TaglibSharp/Riff/WaveFormatEx.cs +++ b/src/TaglibSharp/Riff/WaveFormatEx.cs -@@ -179,6 +179,18 @@ public WaveFormatEx (ByteVector data, int offset) +@@ -179,6 +179,18 @@ public int AudioBitrate { } } @@ -1312,7 +1369,7 @@ diff --git a/src/TaglibSharp/Tag.cs b/src/TaglibSharp/Tag.cs index 9e5c00b..4ef6fc5 100644 --- a/src/TaglibSharp/Tag.cs +++ b/src/TaglibSharp/Tag.cs -@@ -1078,6 +1078,66 @@ public abstract class Tag +@@ -1078,6 +1078,66 @@ public virtual string Length { set { } } @@ -1380,7 +1437,7 @@ index 9e5c00b..4ef6fc5 100644 /// Gets and sets a collection of pictures associated with /// the media represented by the current instance. diff --git a/src/TaglibSharp/TaglibSharp.csproj b/src/TaglibSharp/TaglibSharp.csproj -index 88ac00d..aec9676 100644 +index 23bdd8e..cb4bd9c 100644 --- a/src/TaglibSharp/TaglibSharp.csproj +++ b/src/TaglibSharp/TaglibSharp.csproj @@ -1,6 +1,8 @@ @@ -1388,7 +1445,7 @@ index 88ac00d..aec9676 100644 + 2.3.0.0 -+ net47;netstandard2.0 ++ netstandard2.1 $(LibTargetFrameworks) bin\$(Configuration)\$(TargetFramework)\TaglibSharp.xml TagLibSharp diff --git a/collect_files.bat b/collect_files.bat index f65e179e7..5b5d0ceb6 100644 --- a/collect_files.bat +++ b/collect_files.bat @@ -1,140 +1,109 @@ -@ECHO OFF -REM This script collects the built .exe, .dll etc. files required for running CUETools -REM Wolfgang Stöggl , 2020-2025. - -REM The script is located in the subdirectory CUETools -echo %~dp0 -pushd %~dp0 -SET base_dir=. - -REM Get version of CUETools -for /f "tokens=7 delims= " %%a in ('find "CUEToolsVersion =" %base_dir%\CUETools.Processor\CUESheet.cs') do set PRODUCTVER=%%a -REM echo %PRODUCTVER% -REM "2.1.7"; - -REM Remove double quotes and semicolon -set PRODUCTVER=%PRODUCTVER:"=% -set PRODUCTVER=%PRODUCTVER:;=% -echo CUETools version: %PRODUCTVER% - -SET release_dir=%base_dir%\bin\Release\CUETools_%PRODUCTVER% - -mkdir %release_dir% - -REM use xcopy instead of copy. xcopy creates directories if necessary and outputs the copied file -REM /Y Suppresses prompting to confirm that you want to overwrite an existing destination file. -REM /D xcopy copies all Source files that are newer than existing Destination files - -REM 32 files from net47 -xcopy /Y /D %base_dir%\bin\Release\net47\BluTools.exe %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\BluTools.exe.config %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUEControls.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUERipper.exe %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUERipper.exe.config %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.exe %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.AccurateRip.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.ALACEnc.exe %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.ALACEnc.exe.config %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.ARCUE.exe %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.CDImage.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.Codecs.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.Compression.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.Converter.exe %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.CTDB.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.CTDB.Types.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.eac3to.exe %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.eac3to.exe.config %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.exe.config %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.FLACCL.cmd.exe %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.FLACCL.cmd.exe.config %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.Flake.exe %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.Flake.exe.config %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.Parity.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.Processor.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.Ripper.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.Ripper.Console.exe %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.Ripper.Console.exe.config %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\DeviceId.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\Freedb.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\ProgressODoom.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\TagLibSharp.dll %release_dir% - -xcopy /Y /D %base_dir%\License.txt %release_dir% -xcopy /Y /D %base_dir%\CUETools\user_profiles_enabled %release_dir% - -xcopy /Y /D %base_dir%\bin\Release\net47\de-DE\* %release_dir%\de-DE\ -xcopy /Y /D %base_dir%\bin\Release\net47\ru-RU\* %release_dir%\ru-RU\ - -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\Bwg.Hardware.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\Bwg.Logging.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\Bwg.Scsi.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\CUETools.Codecs.ALAC.dll %release_dir%\plugins\ -REM No more win32\CUETools.Codecs.BDLPCM.dll and win32\CUETools.Codecs.BDLPCM.dll. Instead: CUETools.Codecs.MPEG.dll -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\CUETools.Codecs.MPEG.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\CUETools.Codecs.FLACCL.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\CUETools.Codecs.Flake.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\CUETools.Codecs.HDCD.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\CUETools.Codecs.libFLAC.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\CUETools.Codecs.libmp3lame.dll %release_dir%\plugins\ -REM No more win32\CUETools.Codecs.WavPack.dll and x64\CUETools.Codecs.WavPack.dll. Instead: CUETools.Codecs.libwavpack.dll -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\CUETools.Codecs.libwavpack.dll %release_dir%\plugins\ -REM Nor more win32\CUETools.Codecs.APE.dll and x64\CUETools.Codecs.APE.dll. Instead: CUETools.Codecs.MACLib.dll -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\CUETools.Codecs.MACLib.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\CUETools.Codecs.WMA.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\CUETools.Compression.Zip.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\CUETools.Ripper.SCSI.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\FFmpeg.AutoGen.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\flac.cl %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\OpenCLNet.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\WindowsMediaLib.dll %release_dir%\plugins\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\CUETools.Codecs.ffmpegdll.dll %release_dir%\plugins\ - -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\win32\CUETools.Codecs.TTA.dll %release_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\win32\CUETools.Compression.Rar.dll %release_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\x64\CUETools.Codecs.TTA.dll %release_dir%\plugins\x64\ -REM CUETools.Compression.Rar.dll is the same in the win32 and x64 directory of 2.1.7. Copy from win32 -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\win32\CUETools.Compression.Rar.dll %release_dir%\plugins\x64\ - -REM plugins translation files -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\de-DE\* %release_dir%\plugins\de-DE\ -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\ru-RU\* %release_dir%\plugins\ru-RU\ - -REM ThirdParty -xcopy /Y /D %base_dir%\ThirdParty\ICSharpCode.SharpZipLib.dll %release_dir%\plugins\ - -REM ThirdParty\Win32 plugins -xcopy /Y /D %base_dir%\ThirdParty\Win32\hdcd.dll %release_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\ThirdParty\Win32\libFLAC_dynamic.dll %release_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\ThirdParty\Win32\libmp3lame.dll %release_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\ThirdParty\Win32\MACLibDll.dll %release_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\ThirdParty\Win32\unrar.dll %release_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\ThirdParty\Win32\wavpackdll.dll %release_dir%\plugins\win32\ - -REM ThirdParty\x64 plugins -xcopy /Y /D %base_dir%\ThirdParty\x64\hdcd.dll %release_dir%\plugins\x64\ -xcopy /Y /D %base_dir%\ThirdParty\x64\libFLAC_dynamic.dll %release_dir%\plugins\x64\ -xcopy /Y /D %base_dir%\ThirdParty\x64\libmp3lame.dll %release_dir%\plugins\x64\ -xcopy /Y /D %base_dir%\ThirdParty\x64\MACLibDll.dll %release_dir%\plugins\x64\ -xcopy /Y /D %base_dir%\ThirdParty\x64\Unrar.dll %release_dir%\plugins\x64\ -xcopy /Y /D %base_dir%\ThirdParty\x64\wavpackdll.dll %release_dir%\plugins\x64\ - -REM EAC Plugin -REM CUETools.CTDB.Types.dll is also required now -xcopy /Y /D %base_dir%\bin\Release\interop\EAC\CUETools.AccurateRip.dll %release_dir%\interop\EAC\ -xcopy /Y /D %base_dir%\bin\Release\interop\EAC\CUETools.CDImage.dll %release_dir%\interop\EAC\ -xcopy /Y /D %base_dir%\bin\Release\interop\EAC\CUETools.Codecs.dll %release_dir%\interop\EAC\ -xcopy /Y /D %base_dir%\bin\Release\interop\EAC\CUETools.CTDB.dll %release_dir%\interop\EAC\ -xcopy /Y /D %base_dir%\bin\Release\interop\EAC\CUETools.CTDB.EACPlugin.dll %release_dir%\interop\EAC\ -xcopy /Y /D %base_dir%\bin\Release\interop\EAC\CUETools.CTDB.Types.dll %release_dir%\interop\EAC\ -xcopy /Y /D %base_dir%\bin\Release\interop\EAC\CUETools.Parity.dll %release_dir%\interop\EAC\ -xcopy /Y /D %base_dir%\bin\Release\interop\EAC\Newtonsoft.Json.dll %release_dir%\interop\EAC\ - -REM required for running CUERipper: -REM Newtonsoft.Json.dll -xcopy /Y /D %base_dir%\bin\Release\net47\plugins\Newtonsoft.Json.dll %release_dir% - -REM CUETools.LossyWAV.exe was not in 2.1.7 release, added -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.Codecs.LossyWAV.dll %release_dir% -xcopy /Y /D %base_dir%\bin\Release\net47\CUETools.LossyWAV.exe %release_dir% - -popd +@ECHO OFF +REM Collect the built .exe, .dll, .json, and config files required for running CUETools. +REM Wolfgang Stoeggl , 2020-2026. + +REM The script is located in the CUETools repository root. +echo %~dp0 +pushd %~dp0 +SET base_dir=. + +REM Get version of CUETools +for /f "tokens=7 delims= " %%a in ('find "CUEToolsVersion =" %base_dir%\CUETools.Processor\CUESheet.cs') do set PRODUCTVER=%%a +REM echo %PRODUCTVER% +REM "2.1.7"; + +REM Remove double quotes and semicolon +set PRODUCTVER=%PRODUCTVER:"=% +set PRODUCTVER=%PRODUCTVER:;=% +echo CUETools version: %PRODUCTVER% + +SET release_dir=%base_dir%\bin\Release\CUETools_%PRODUCTVER% +SET win_dir=%base_dir%\bin\Release\net10.0-windows +SET cli_dir=%base_dir%\bin\Release\net10.0 +SET lib_dir=%base_dir%\bin\Release\netstandard2.1 + +if not exist "%release_dir%" mkdir "%release_dir%" + +REM use xcopy instead of copy. xcopy creates directories if necessary and outputs the copied file. +REM /Y Suppresses prompting to confirm that you want to overwrite an existing destination file. +REM /D xcopy copies all Source files that are newer than existing Destination files. +REM /I treats the destination as a directory and avoids interactive prompts in batch mode. + +REM Windows applications and their runtime metadata. +xcopy /Y /D /I "%win_dir%\*.exe" "%release_dir%\" +xcopy /Y /D /I "%win_dir%\*.dll" "%release_dir%\" +xcopy /Y /D /I "%win_dir%\*.deps.json" "%release_dir%\" +xcopy /Y /D /I "%win_dir%\*.runtimeconfig.json" "%release_dir%\" +xcopy /Y /D /I "%win_dir%\*.config" "%release_dir%\" +xcopy /Y /D /I "%win_dir%\de-DE\*" "%release_dir%\de-DE\" +xcopy /Y /D /I "%win_dir%\ru-RU\*" "%release_dir%\ru-RU\" + +REM Console tools and their runtime metadata. +xcopy /Y /D /I "%cli_dir%\*.exe" "%release_dir%\" +xcopy /Y /D /I "%cli_dir%\*.dll" "%release_dir%\" +xcopy /Y /D /I "%cli_dir%\*.deps.json" "%release_dir%\" +xcopy /Y /D /I "%cli_dir%\*.runtimeconfig.json" "%release_dir%\" +xcopy /Y /D /I "%cli_dir%\*.config" "%release_dir%\" + +xcopy /Y /D /I "%base_dir%\License.txt" "%release_dir%\" +xcopy /Y /D /I "%base_dir%\CUETools\user_profiles_enabled" "%release_dir%\" + +REM Managed plugins. Most plugins are libraries under netstandard2.1; FLACCL remains Windows-only. +xcopy /Y /D /I /S "%lib_dir%\plugins\*.dll" "%release_dir%\plugins\" +xcopy /Y /D /I /S "%lib_dir%\plugins\*.deps.json" "%release_dir%\plugins\" +xcopy /Y /D /I /S "%win_dir%\plugins\*.dll" "%release_dir%\plugins\" +xcopy /Y /D /I /S "%win_dir%\plugins\*.deps.json" "%release_dir%\plugins\" +xcopy /Y /D /I /S "%win_dir%\plugins\*.cl" "%release_dir%\plugins\" + +REM ThirdParty +IF EXIST "%base_dir%\ThirdParty\ICSharpCode.SharpZipLib.dll" ( + xcopy /Y /D /I "%base_dir%\ThirdParty\ICSharpCode.SharpZipLib.dll" "%release_dir%\plugins\" +) ELSE echo WARNING: Missing %base_dir%\ThirdParty\ICSharpCode.SharpZipLib.dll + +REM ThirdParty\Win32 plugins +IF EXIST "%base_dir%\ThirdParty\Win32\hdcd.dll" ( + xcopy /Y /D /I "%base_dir%\ThirdParty\Win32\hdcd.dll" "%release_dir%\plugins\win32\" +) ELSE echo WARNING: Missing %base_dir%\ThirdParty\Win32\hdcd.dll +IF EXIST "%base_dir%\ThirdParty\Win32\libFLAC_dynamic.dll" ( + xcopy /Y /D /I "%base_dir%\ThirdParty\Win32\libFLAC_dynamic.dll" "%release_dir%\plugins\win32\" +) ELSE echo WARNING: Missing %base_dir%\ThirdParty\Win32\libFLAC_dynamic.dll +IF EXIST "%base_dir%\ThirdParty\Win32\libmp3lame.dll" ( + xcopy /Y /D /I "%base_dir%\ThirdParty\Win32\libmp3lame.dll" "%release_dir%\plugins\win32\" +) ELSE echo WARNING: Missing %base_dir%\ThirdParty\Win32\libmp3lame.dll +IF EXIST "%base_dir%\ThirdParty\Win32\MACLibDll.dll" ( + xcopy /Y /D /I "%base_dir%\ThirdParty\Win32\MACLibDll.dll" "%release_dir%\plugins\win32\" +) ELSE echo WARNING: Missing %base_dir%\ThirdParty\Win32\MACLibDll.dll +IF EXIST "%base_dir%\ThirdParty\Win32\unrar.dll" ( + xcopy /Y /D /I "%base_dir%\ThirdParty\Win32\unrar.dll" "%release_dir%\plugins\win32\" +) ELSE echo WARNING: Missing %base_dir%\ThirdParty\Win32\unrar.dll +IF EXIST "%base_dir%\ThirdParty\Win32\wavpackdll.dll" ( + xcopy /Y /D /I "%base_dir%\ThirdParty\Win32\wavpackdll.dll" "%release_dir%\plugins\win32\" +) ELSE echo WARNING: Missing %base_dir%\ThirdParty\Win32\wavpackdll.dll + +REM ThirdParty\x64 plugins +IF EXIST "%base_dir%\ThirdParty\x64\hdcd.dll" ( + xcopy /Y /D /I "%base_dir%\ThirdParty\x64\hdcd.dll" "%release_dir%\plugins\x64\" +) ELSE echo WARNING: Missing %base_dir%\ThirdParty\x64\hdcd.dll +IF EXIST "%base_dir%\ThirdParty\x64\libFLAC_dynamic.dll" ( + xcopy /Y /D /I "%base_dir%\ThirdParty\x64\libFLAC_dynamic.dll" "%release_dir%\plugins\x64\" +) ELSE echo WARNING: Missing %base_dir%\ThirdParty\x64\libFLAC_dynamic.dll +IF EXIST "%base_dir%\ThirdParty\x64\libmp3lame.dll" ( + xcopy /Y /D /I "%base_dir%\ThirdParty\x64\libmp3lame.dll" "%release_dir%\plugins\x64\" +) ELSE echo WARNING: Missing %base_dir%\ThirdParty\x64\libmp3lame.dll +IF EXIST "%base_dir%\ThirdParty\x64\MACLibDll.dll" ( + xcopy /Y /D /I "%base_dir%\ThirdParty\x64\MACLibDll.dll" "%release_dir%\plugins\x64\" +) ELSE echo WARNING: Missing %base_dir%\ThirdParty\x64\MACLibDll.dll +IF EXIST "%base_dir%\ThirdParty\x64\Unrar.dll" ( + xcopy /Y /D /I "%base_dir%\ThirdParty\x64\Unrar.dll" "%release_dir%\plugins\x64\" +) ELSE echo WARNING: Missing %base_dir%\ThirdParty\x64\Unrar.dll +IF EXIST "%base_dir%\ThirdParty\x64\wavpackdll.dll" ( + xcopy /Y /D /I "%base_dir%\ThirdParty\x64\wavpackdll.dll" "%release_dir%\plugins\x64\" +) ELSE echo WARNING: Missing %base_dir%\ThirdParty\x64\wavpackdll.dll + +REM EAC Plugin +xcopy /Y /D /I "%win_dir%\interop\EAC\*.dll" "%release_dir%\interop\EAC\" +xcopy /Y /D /I "%win_dir%\interop\EAC\*.deps.json" "%release_dir%\interop\EAC\" +xcopy /Y /D /I "%win_dir%\interop\EAC\*.config" "%release_dir%\interop\EAC\" +xcopy /Y /D /I "%win_dir%\Newtonsoft.Json.dll" "%release_dir%\interop\EAC\" + +popd diff --git a/collect_files_debug.bat b/collect_files_debug.bat index 570a4305d..dac9b9c93 100644 --- a/collect_files_debug.bat +++ b/collect_files_debug.bat @@ -1,42 +1,57 @@ -@ECHO OFF -REM This script collects built .dll and .pdb files from ThirdParty for debugging CUETools. -REM Wolfgang Stöggl , 2020-2025. - -REM The script is located in the subdirectory CUETools -echo %~dp0 -pushd %~dp0 -SET base_dir=. -SET debug_dir=%base_dir%\bin\Debug\net47 - -REM use xcopy instead of copy. xcopy creates directories if necessary and outputs the copied file -REM /Y Suppresses prompting to confirm that you want to overwrite an existing destination file. -REM /D xcopy copies all Source files that are newer than existing Destination files - -REM xcopy /Y /D %base_dir%\CUETools\user_profiles_enabled %debug_dir% - -REM ThirdParty -xcopy /Y /D %base_dir%\ThirdParty\ICSharpCode.SharpZipLib.dll %debug_dir%\plugins\ - -REM ThirdParty\Win32 plugins -xcopy /Y /D %base_dir%\ThirdParty\Win32\hdcd.dll %debug_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\ThirdPartyDebug\Win32\libFLAC_dynamic.dll %debug_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\ThirdPartyDebug\Win32\libFLAC_dynamic.pdb %debug_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\ThirdParty\Win32\libmp3lame.dll %debug_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\ThirdPartyDebug\Win32\MACLibDll.dll %debug_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\ThirdPartyDebug\Win32\MACLibDll.pdb %debug_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\ThirdParty\Win32\unrar.dll %debug_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\ThirdPartyDebug\Win32\wavpackdll.dll %debug_dir%\plugins\win32\ -xcopy /Y /D %base_dir%\ThirdPartyDebug\Win32\wavpackdll.pdb %debug_dir%\plugins\win32\ - -REM ThirdParty\x64 plugins -xcopy /Y /D %base_dir%\ThirdParty\x64\hdcd.dll %debug_dir%\plugins\x64\ -xcopy /Y /D %base_dir%\ThirdPartyDebug\x64\libFLAC_dynamic.dll %debug_dir%\plugins\x64\ -xcopy /Y /D %base_dir%\ThirdPartyDebug\x64\libFLAC_dynamic.pdb %debug_dir%\plugins\x64\ -xcopy /Y /D %base_dir%\ThirdParty\x64\libmp3lame.dll %debug_dir%\plugins\x64\ -xcopy /Y /D %base_dir%\ThirdPartyDebug\x64\MACLibDll.dll %debug_dir%\plugins\x64\ -xcopy /Y /D %base_dir%\ThirdPartyDebug\x64\MACLibDll.pdb %debug_dir%\plugins\x64\ -xcopy /Y /D %base_dir%\ThirdParty\x64\Unrar.dll %debug_dir%\plugins\x64\ -xcopy /Y /D %base_dir%\ThirdPartyDebug\x64\wavpackdll.dll %debug_dir%\plugins\x64\ -xcopy /Y /D %base_dir%\ThirdPartyDebug\x64\wavpackdll.pdb %debug_dir%\plugins\x64\ - -popd +@ECHO OFF +REM Collect native ThirdParty .dll and .pdb files for debugging CUETools. +REM Wolfgang Stoeggl , 2020-2026. + +REM The script is located in the CUETools repository root. +echo %~dp0 +pushd %~dp0 +SET base_dir=. +SET debug_win_dir=%base_dir%\bin\Debug\net10.0-windows +SET debug_lib_dir=%base_dir%\bin\Debug\netstandard2.1 + +REM use xcopy instead of copy. xcopy creates directories if necessary and outputs the copied file. +REM /Y Suppresses prompting to confirm that you want to overwrite an existing destination file. +REM /D xcopy copies all Source files that are newer than existing Destination files. + +call :copy_native "%debug_win_dir%" +call :copy_native "%debug_lib_dir%" + +popd +goto :eof + +:copy_native +SET target_dir=%~1 + +REM ThirdParty +call :copy_file "%base_dir%\ThirdParty\ICSharpCode.SharpZipLib.dll" "%target_dir%\plugins\" + +REM ThirdParty\Win32 plugins +call :copy_file "%base_dir%\ThirdParty\Win32\hdcd.dll" "%target_dir%\plugins\win32\" +call :copy_file "%base_dir%\ThirdPartyDebug\Win32\libFLAC_dynamic.dll" "%target_dir%\plugins\win32\" +call :copy_file "%base_dir%\ThirdPartyDebug\Win32\libFLAC_dynamic.pdb" "%target_dir%\plugins\win32\" +call :copy_file "%base_dir%\ThirdParty\Win32\libmp3lame.dll" "%target_dir%\plugins\win32\" +call :copy_file "%base_dir%\ThirdPartyDebug\Win32\MACLibDll.dll" "%target_dir%\plugins\win32\" +call :copy_file "%base_dir%\ThirdPartyDebug\Win32\MACLibDll.pdb" "%target_dir%\plugins\win32\" +call :copy_file "%base_dir%\ThirdParty\Win32\unrar.dll" "%target_dir%\plugins\win32\" +call :copy_file "%base_dir%\ThirdPartyDebug\Win32\wavpackdll.dll" "%target_dir%\plugins\win32\" +call :copy_file "%base_dir%\ThirdPartyDebug\Win32\wavpackdll.pdb" "%target_dir%\plugins\win32\" + +REM ThirdParty\x64 plugins +call :copy_file "%base_dir%\ThirdParty\x64\hdcd.dll" "%target_dir%\plugins\x64\" +call :copy_file "%base_dir%\ThirdPartyDebug\x64\libFLAC_dynamic.dll" "%target_dir%\plugins\x64\" +call :copy_file "%base_dir%\ThirdPartyDebug\x64\libFLAC_dynamic.pdb" "%target_dir%\plugins\x64\" +call :copy_file "%base_dir%\ThirdParty\x64\libmp3lame.dll" "%target_dir%\plugins\x64\" +call :copy_file "%base_dir%\ThirdPartyDebug\x64\MACLibDll.dll" "%target_dir%\plugins\x64\" +call :copy_file "%base_dir%\ThirdPartyDebug\x64\MACLibDll.pdb" "%target_dir%\plugins\x64\" +call :copy_file "%base_dir%\ThirdParty\x64\Unrar.dll" "%target_dir%\plugins\x64\" +call :copy_file "%base_dir%\ThirdPartyDebug\x64\wavpackdll.dll" "%target_dir%\plugins\x64\" +call :copy_file "%base_dir%\ThirdPartyDebug\x64\wavpackdll.pdb" "%target_dir%\plugins\x64\" +exit /b 0 + +:copy_file +if exist "%~1" ( + xcopy /Y /D /I "%~1" "%~2" +) else ( + echo WARNING: Missing %~1 +) +exit /b 0 diff --git a/docs/superpowers/plans/2026-05-27-accuraterip-meta.md b/docs/superpowers/plans/2026-05-27-accuraterip-meta.md new file mode 100644 index 000000000..0cd7f4902 --- /dev/null +++ b/docs/superpowers/plans/2026-05-27-accuraterip-meta.md @@ -0,0 +1,1804 @@ +# AccurateRip Meta Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace active FreeDB metadata lookup with AccurateRip Meta while keeping the old FreeDB code dormant and unreachable from normal UI. + +**Architecture:** Add a focused AccurateRip Meta provider in `CUETools.Processor` with a testable JSON mapper and injectable HTTP transport. Ground the request and response contract in the local EasyEACGUI sample plus a recorded live Amarok fixture before writing the mapper; the live endpoint expects a prefixed id such as `001-00041f6d-00083ece-020e1201`, not the bare `AccurateRipVerify.CalculateAccurateRipId` value. The endpoint is HTTP and expects `Content-Type: application/x-www-form-urlencoded` with a JSON body; this deliberately mirrors the deployed EasyEACGUI contract and should not be changed to HTTPS or `application/json` without live verification. Treat returned metadata and artwork as ordinary unauthenticated metadata, matching the existing AccurateRip HTTP precedent, not as security-sensitive input. Integrate it into `CUESheet.LookupAlbumInfo` and CUERipper reload/fallback lookup, using the existing `CUEMetadata` and `AlbumArt` models while validating provider cover URLs before consumers fetch them. In CUETools, `CTDBMetadataSearch.Extensive` must query AccurateRip Meta even when CTDB already returned metadata. In CUERipper, preserve the current fallback/reload shape: query AccurateRip Meta when no release was found or when the user requests all metadata/reload, not on every ordinary rip where CTDB already populated the release list. Hide FreeDB submission/settings paths, intentionally retire FreeDB submit/repair actions because AccurateRip Meta has no submission API, and preserve old FreeDB code as fenced source, not as a flip-a-symbol reactivation path, because project references are removed from the normal solution. + +**Tech Stack:** C#/.NET 10 Windows projects, `netstandard2.1` processor library, MSTest, `Newtonsoft.Json`, WinForms, existing CUETools metadata and CTDB cover-art models. + +--- + +## File Structure + +- Create `CUETools.Processor/AccurateRipMeta.cs` + - Owns AccurateRip Meta constants, response contracts, mapper, provider, and HTTP transport. + - Public surface: `AccurateRipMeta.SourceKey`, `AccurateRipMeta.DisplayName`, `AccurateRipMeta.TryParseMetadata(string json, CDImageLayout toc, out CUEMetadataEntry entry)`, `AccurateRipMetaProvider.Lookup(CDImageLayout toc, IWebProxy proxy)`. + +- Create `CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs` + - Tests recorded fixture mapping, JSON mapping, unused fields, track-number ordering, track-count/shape rejection, extra-track tolerance, invalid cover URL rejection, odd scalar/cover-art shapes, transport request body, proxy forwarding, fail-soft provider behavior, CTDB-succeeded Extensive behavior, and FreeDB settings dormancy. + +- Create `CUETools/CUETools.TestProcessor/Fixtures/accuraterip-meta-amarok.json` + - Recorded live response for `{"accurateripdiscid":"001-00041f6d-00083ece-020e1201"}` from `http://meta.accuraterip.com/discmatch`, captured while reviewing this plan. + +- Modify `CUETools/CUETools.TestProcessor/CUETools.TestProcessor.csproj` + - Include `AccurateRipMetaTest.cs`. + - Remove the FreeDB project reference after FreeDB-dependent production code is isolated. + +- Modify `CUETools/CUETools.TestCodecs/CUETools.TestCodecs.csproj` + - Remove the FreeDB project reference so the active solution build no longer pulls in `Freedb/Freedb.csproj` through codec tests. + +- Modify `CUETools.Processor/CUESheet.cs` + - Remove active `using Freedb`. + - Add an injectable `AccurateRipMetaProvider` property for tests. + - Add a protected CTDB metadata lookup seam so tests can cover the CTDB-succeeded Extensive branch without network I/O. + - Replace the active FreeDB fallback with AccurateRip Meta lookup during `CTDBMetadataSearch.Extensive`. + - Keep old FreeDB lookup and `TocFromCDEntry` dormant under `#if DORMANT_FREEDB`. + +- Modify `CUETools.Processor/CUEMetadata.cs` + - Keep active metadata behavior unchanged. + - Move `FillFromFreedb`, `FreedbToEncoding`, and `FreedbToVarious` under `#if DORMANT_FREEDB`. + +- Modify `CUETools.Processor/CUEConfigAdvanced.cs` + - Move `FreedbUser`, `FreedbDomain`, and `FreedbSiteAddress` under `#if DORMANT_FREEDB`. + - This removes the normal CUETools settings exposure because `CUETools/frmSettings.cs` binds `propertyGrid1.SelectedObject` directly to `_config.advanced`. + +- Modify `CUETools.Processor/CUETools.Processor.csproj` + - Remove the FreeDB project reference once the active processor code no longer compiles against it. + +- Modify `CUETools/frmChoice.cs` + - Register the `accurateripmeta` image key with the existing AccurateRip icon at runtime. + - Remove active FreeDB encoding duplicate insertion; keep it dormant under `#if DORMANT_FREEDB`. + +- Modify `CUETools/CUETools.csproj` + - Remove the FreeDB project reference once active UI code no longer compiles against it. + +- Modify `CUERipper/frmCUERipper.cs` + - Remove active `using Freedb`. + - Register the `accurateripmeta` image key with the existing AccurateRip icon at runtime. + - Replace active FreeDB lookup with AccurateRip Meta lookup during no-result and reload/all-metadata lookup. + - Fetch `AlbumArt` entries for every release in `backgroundWorkerArtwork_DoWork` before the CTDB-only artwork scan so AccurateRip Meta cover URLs appear in rip-time preview/download even when the user selects a non-default release later. + - Hide FreeDB submit and FreeDB repair buttons from normal runtime. + - Keep old FreeDB lookup/submission/repair handlers dormant under `#if DORMANT_FREEDB`, with non-dormant event-handler stubs where designer wiring still requires a method. + - Leave `frmFreedbSubmit` compiled but unreachable from normal UI; the dormant submit code is the only remaining constructor path. Removing the form/designer/resx files is a later cleanup after the dormant retention period. + +- Modify `CUERipper/Options.cs` + - Remove the normal `FreedbSiteAddress` property-grid entry. + - Keep a dormant copy under `#if DORMANT_FREEDB`. + +- Modify `CUERipper/CUERipper.csproj` + - Remove the FreeDB project reference once active CUERipper code no longer compiles against it. + +- Modify `CUETools.sln` + - Remove `Freedb/Freedb.csproj` from the active solution so FreeDB is not part of the normal build. + - Leave the `Freedb/` source directory intact for future adaptation. Defining `DORMANT_FREEDB` later will also require restoring the `Freedb` project references. + +- Exclude from this plan: `CUETools.CTDB.EACPlugin` + - It has its own CTDB metadata UI with `source == "freedb"` repair logic and a `freedb` image key, but no `Freedb.csproj` dependency. Keep it unchanged because the user scoped this replacement to CUETools/CUERipper active FreeDB provider behavior, not the EAC plugin's CTDB metadata display. + +--- + +### Task 1: Record AccurateRip Meta Contract Fixture And Mapper Tests + +**Files:** +- Create: `CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs` +- Create: `CUETools/CUETools.TestProcessor/Fixtures/accuraterip-meta-amarok.json` +- Modify: `CUETools/CUETools.TestProcessor/CUETools.TestProcessor.csproj` + +- [ ] **Step 1: Record the verified Amarok response fixture** + +Create `CUETools/CUETools.TestProcessor/Fixtures/accuraterip-meta-amarok.json`: + +```json +{"_tocid":"858576","Artist":"Mike Oldfield","Album":"Amarok","Year":"1990","Genre":"Parlata","Label":"Virgin","Styles":"Progressive Electronic\rProg-Rock\rArt Rock","CatalogNum":"CDV 2640","UPC":"5012981264024","_albumid":"155298","_arturl":"http://meta.accuraterip.com/albumart/0000001F89DA34F26C1A","tracks":[{"TrackNumber":"1","Title":"Amarok","Artist":"Mike Oldfield","Composers":"","Conductors":"","ISRC":"GBAAA0000417"}]} +``` + +This fixture is the response returned by a live POST captured on 2026-05-28 with the local EasyEACGUI request shape from `H:\CProjekte\EasyEACGUI\MetadataProvider\AccurateRipMetadata.cs` and `H:\CProjekte\EasyEACGUI\MetadataProvider\BasicTools.cs`. `Label`, `CatalogNum`, `UPC`, and per-track `ISRC` values in the fixture are from that live response, not inferred from the EasyEACGUI consumer code. + +```http +POST http://meta.accuraterip.com/discmatch +User-Agent: Mozilla/5.0 +Content-Type: application/x-www-form-urlencoded + +{"accurateripdiscid":"001-00041f6d-00083ece-020e1201"} +``` + +- [ ] **Step 2: Write the failing mapper tests** + +Create `CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs`: + +```csharp +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Net; +using CUETools.AccurateRip; +using CUETools.CDImage; +using CUETools.CTDB; +using CUETools.Processor; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace CUETools.TestProcessor +{ + [TestClass] + public class AccurateRipMetaTest + { + [TestMethod] + public void TryParseMetadataMapsRecordedAmarokFixture() + { + string json = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Fixtures", "accuraterip-meta-amarok.json")); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateAmarokToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.IsNotNull(entry); + Assert.AreEqual(AccurateRipMeta.SourceKey, entry.ImageKey); + Assert.AreEqual("Mike Oldfield", entry.metadata.Artist); + Assert.AreEqual("Amarok", entry.metadata.Title); + Assert.AreEqual("1990", entry.metadata.Year); + Assert.AreEqual("Parlata", entry.metadata.Genre); + Assert.AreEqual("Virgin", entry.metadata.Label); + Assert.AreEqual("CDV 2640", entry.metadata.LabelNo); + Assert.AreEqual("5012981264024", entry.metadata.Barcode); + Assert.AreEqual(1, entry.metadata.Tracks.Count); + Assert.AreEqual("Amarok", entry.metadata.Tracks[0].Title); + Assert.AreEqual("Mike Oldfield", entry.metadata.Tracks[0].Artist); + Assert.AreEqual("GBAAA0000417", entry.metadata.Tracks[0].ISRC); + Assert.AreEqual(1, entry.metadata.AlbumArt.Count); + Assert.AreEqual("http://meta.accuraterip.com/albumart/0000001F89DA34F26C1A", entry.metadata.AlbumArt[0].uri); + } + + [TestMethod] + public void TryParseMetadataMapsKnownFields() + { + bool parsed = AccurateRipMeta.TryParseMetadata(SampleJson(trackCount: 2), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.IsNotNull(entry); + Assert.AreEqual(AccurateRipMeta.SourceKey, entry.ImageKey); + Assert.AreEqual("The Artist & Guest", entry.metadata.Artist); + Assert.AreEqual("The Album", entry.metadata.Title); + Assert.AreEqual("1999", entry.metadata.Year); + Assert.AreEqual("Rock", entry.metadata.Genre); + Assert.AreEqual("0123456789012", entry.metadata.Barcode); + Assert.AreEqual("The Label", entry.metadata.Label); + Assert.AreEqual("CAT-123", entry.metadata.LabelNo); + Assert.AreEqual("", entry.metadata.Comment); + Assert.AreEqual(2, entry.metadata.Tracks.Count); + Assert.AreEqual("Track One", entry.metadata.Tracks[0].Title); + Assert.AreEqual("Track Artist 1 & Other", entry.metadata.Tracks[0].Artist); + Assert.AreEqual("USAAA0100001", entry.metadata.Tracks[0].ISRC); + Assert.AreEqual("", entry.metadata.Tracks[0].Comment); + Assert.AreEqual("Track Two", entry.metadata.Tracks[1].Title); + Assert.AreEqual("Track Artist 2", entry.metadata.Tracks[1].Artist); + Assert.AreEqual("USAAA0100002", entry.metadata.Tracks[1].ISRC); + Assert.AreEqual(1, entry.metadata.AlbumArt.Count); + Assert.AreEqual("http://covers.example/front.jpg", entry.metadata.AlbumArt[0].uri); + Assert.AreEqual("http://covers.example/front.jpg", entry.metadata.AlbumArt[0].uri150); + Assert.IsTrue(entry.metadata.AlbumArt[0].primary); + } + + [TestMethod] + public void TryParseMetadataIgnoresFieldsWithoutStorage() + { + bool parsed = AccurateRipMeta.TryParseMetadata(SampleJson(trackCount: 2), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("Rock", entry.metadata.Genre); + Assert.IsFalse(entry.metadata.Genre.Contains("Fusion")); + Assert.AreEqual("", entry.metadata.Comment); + Assert.IsFalse(entry.metadata.Comment.Contains("Fusion")); + Assert.IsFalse(entry.metadata.Tracks.Any(track => track.Comment.Contains("Composer"))); + Assert.IsFalse(entry.metadata.Tracks.Any(track => track.Comment.Contains("Conductor"))); + } + + [TestMethod] + public void TryParseMetadataOrdersTracksByTrackNumber() + { + JObject json = JObject.Parse(SampleJson(trackCount: 2)); + JArray tracks = (JArray)json["tracks"]; + json["tracks"] = new JArray(tracks[1], tracks[0]); + + bool parsed = AccurateRipMeta.TryParseMetadata(json.ToString(), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("Track One", entry.metadata.Tracks[0].Title); + Assert.AreEqual("Track Two", entry.metadata.Tracks[1].Title); + Assert.AreEqual("USAAA0100001", entry.metadata.Tracks[0].ISRC); + Assert.AreEqual("USAAA0100002", entry.metadata.Tracks[1].ISRC); + } + + [TestMethod] + public void TryParseMetadataRejectsMissingTrackNumber() + { + string json = SampleJson(trackCount: 2).Replace("\"TrackNumber\":\"2\",", ""); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsFalse(parsed); + Assert.IsNull(entry); + } + + [TestMethod] + public void TryParseMetadataRejectsTrackCountMismatch() + { + bool parsed = AccurateRipMeta.TryParseMetadata(SampleJson(trackCount: 1), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsFalse(parsed); + Assert.IsNull(entry); + } + + [TestMethod] + public void TryParseMetadataRejectsNonArrayTracksShape() + { + JObject json = JObject.Parse(SampleJson(trackCount: 2)); + json["tracks"] = new JObject { ["unexpected"] = "shape" }; + + bool parsed = AccurateRipMeta.TryParseMetadata(json.ToString(), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsFalse(parsed); + Assert.IsNull(entry); + } + + [TestMethod] + public void TryParseMetadataAllowsMissingCoverArt() + { + string json = SampleJson(trackCount: 2).Replace("\"_arturl\":\"http://covers.example/front.jpg\",", ""); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual(0, entry.metadata.AlbumArt.Count); + } + + [TestMethod] + public void TryParseMetadataRejectsInvalidCoverArtUrl() + { + string json = SampleJson(trackCount: 2).Replace("http://covers.example/front.jpg", "not a url"); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual(0, entry.metadata.AlbumArt.Count); + } + + [TestMethod] + public void TryParseMetadataAllowsEmptyOptionalFields() + { + string json = SampleJson(trackCount: 2) + .Replace("\"Year\":\"1999\",", "\"Year\":\"\",") + .Replace("\"Genre\":\"Rock\\rAlternative\",", "\"Genre\":\"\",") + .Replace("\"Label\":\"The Label\",", "\"Label\":\"\",") + .Replace("\"CatalogNum\":\"CAT-123\",", "\"CatalogNum\":\"\",") + .Replace("\"UPC\":\"0123456789012\",", "\"UPC\":\"\","); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("", entry.metadata.Year); + Assert.AreEqual("", entry.metadata.Genre); + Assert.AreEqual("", entry.metadata.Label); + Assert.AreEqual("", entry.metadata.LabelNo); + Assert.AreEqual("", entry.metadata.Barcode); + } + + [TestMethod] + public void TryParseMetadataAllowsExtraProviderTracks() + { + bool parsed = AccurateRipMeta.TryParseMetadata(SampleJson(trackCount: 3), CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual(2, entry.metadata.Tracks.Count); + Assert.AreEqual("Track One", entry.metadata.Tracks[0].Title); + Assert.AreEqual("Track Two", entry.metadata.Tracks[1].Title); + } + + [TestMethod] + public void TryParseMetadataAllowsOddScalarShapesWithoutDroppingMetadata() + { + string json = SampleJson(trackCount: 2) + .Replace("\"Year\":\"1999\"", "\"Year\":1999") + .Replace("\"UPC\":\"0123456789012\"", "\"UPC\":123456789012"); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("1999", entry.metadata.Year); + Assert.AreEqual("123456789012", entry.metadata.Barcode); + } + + [TestMethod] + public void TryParseMetadataIgnoresOddCoverArtShapeWithoutDroppingMetadata() + { + string json = SampleJson(trackCount: 2).Replace("\"_arturl\":\"http://covers.example/front.jpg\"", "\"_arturl\":[\"http://covers.example/front.jpg\"]"); + + bool parsed = AccurateRipMeta.TryParseMetadata(json, CreateTwoTrackToc(), out CUEMetadataEntry entry); + + Assert.IsTrue(parsed); + Assert.AreEqual("The Album", entry.metadata.Title); + Assert.AreEqual(0, entry.metadata.AlbumArt.Count); + } + + private static CDImageLayout CreateAmarokToc() + { + var sheet = new CUESheet(new CUEConfig()); + sheet.Open("Amarok\\Amarok.cue"); + return sheet.TOC; + } + + private static CDImageLayout CreateTwoTrackToc() + { + var toc = new CDImageLayout(); + toc.AddTrack(new CDTrack(1, 0, 15000, true, false)); + toc.AddTrack(new CDTrack(2, 15000, 18000, true, false)); + return toc; + } + + private static string SampleJson(int trackCount) + { + var tracks = new List + { + new + { + TrackNumber = "1", + Title = "Track One", + Artist = "Track Artist 1\rOther", + Composers = "Ignored Composer", + Conductors = "Ignored Conductor", + ISRC = "USAAA0100001" + } + }; + + if (trackCount > 1) + { + tracks.Add(new + { + TrackNumber = "2", + Title = "Track Two", + Artist = "Track Artist 2", + Composers = "Second Composer", + Conductors = "Second Conductor", + ISRC = "USAAA0100002" + }); + } + + if (trackCount > 2) + { + tracks.Add(new + { + TrackNumber = "3", + Title = "Provider Extra Track", + Artist = "Ignored Extra Artist", + Composers = "Ignored Extra Composer", + Conductors = "Ignored Extra Conductor", + ISRC = "USAAA0100003" + }); + } + + return JsonConvert.SerializeObject(new + { + _tocid = "toc-id", + Artist = "The Artist\rGuest", + Album = "The Album", + Year = "1999", + Genre = "Rock\rAlternative", + Label = "The Label", + Styles = "Fusion", + CatalogNum = "CAT-123", + UPC = "0123456789012", + _albumid = "album-id", + _arturl = "http://covers.example/front.jpg", + tracks = tracks + }); + } + } +} +``` + +Modify `CUETools/CUETools.TestProcessor/CUETools.TestProcessor.csproj` and add the compile include beside the existing test files: + +```xml + + + + + + + + + + +``` + +- [ ] **Step 3: Run tests to verify they fail for the expected reason** + +Run: + +```powershell +dotnet test CUETools\CUETools.TestProcessor\CUETools.TestProcessor.csproj --filter AccurateRipMetaTest -v:minimal +``` + +Expected: FAIL at compile time with errors that `AccurateRipMeta` does not exist. + +- [ ] **Step 4: Commit the failing tests and recorded fixture** + +```powershell +git add CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs CUETools/CUETools.TestProcessor/Fixtures/accuraterip-meta-amarok.json CUETools/CUETools.TestProcessor/CUETools.TestProcessor.csproj +git commit -m "test: cover AccurateRip Meta mapping" +``` + +--- + +### Task 2: Implement AccurateRip Meta Mapper + +**Files:** +- Create: `CUETools.Processor/AccurateRipMeta.cs` +- Test: `CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs` + +- [ ] **Step 1: Add the mapper implementation** + +Create `CUETools.Processor/AccurateRipMeta.cs`: + +```csharp +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using CUETools.AccurateRip; +using CUETools.CDImage; +using CUETools.CTDB; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace CUETools.Processor +{ + public static class AccurateRipMeta + { + public const string SourceKey = "accurateripmeta"; + public const string DisplayName = "AccurateRip Meta"; + public const string Endpoint = "http://meta.accuraterip.com/discmatch"; + + public static bool TryParseMetadata(string json, CDImageLayout toc, out CUEMetadataEntry entry) + { + entry = null; + + if (toc == null || string.IsNullOrWhiteSpace(json)) + return false; + + JObject response; + try + { + response = JObject.Parse(json); + } + catch (JsonException ex) + { + System.Diagnostics.Trace.WriteLine("AccurateRip Meta JSON parse failed: " + ex.Message); + return false; + } + + JArray tracks = response["tracks"] as JArray; + int audioTracks = (int)toc.AudioTracks; + if (tracks == null || tracks.Count < audioTracks) + return false; + + List orderedTracks = OrderTracksByTrackNumber(tracks, audioTracks); + if (orderedTracks == null) + return false; + + if (string.IsNullOrWhiteSpace(Clean(response["Artist"])) && string.IsNullOrWhiteSpace(Clean(response["Album"]))) + return false; + + var metadata = new CUEMetadata(toc.TOCID, audioTracks) + { + Artist = NormalizeProviderList(response["Artist"]), + Title = Clean(response["Album"]), + Year = Clean(response["Year"]), + Genre = FirstNonEmptyLine(response["Genre"]), + Barcode = Clean(response["UPC"]), + Label = Clean(response["Label"]), + LabelNo = Clean(response["CatalogNum"]) + }; + + // AccurateRip Meta also exposes Styles, Composers, and Conductors. Keep them unused until CUETools has explicit fields for them; do not overwrite Genre with Styles. + for (int i = 0; i < audioTracks; i++) + { + JObject track = orderedTracks[i]; + + metadata.Tracks[i].Title = Clean(track["Title"]); + metadata.Tracks[i].Artist = NormalizeProviderList(track["Artist"]); + metadata.Tracks[i].ISRC = Clean(track["ISRC"]); + } + + string coverUrl = NormalizeCoverUrl(response["_arturl"]); + if (coverUrl != "") + { + metadata.AlbumArt.Add(new CTDBResponseMetaImage + { + uri = coverUrl, + uri150 = coverUrl, + primary = true + }); + } + + entry = new CUEMetadataEntry(metadata, toc, SourceKey); + return true; + } + + internal static string CreateRequestDiscId(CDImageLayout toc) + { + return string.Format(CultureInfo.InvariantCulture, "{0:000}-{1}", toc.AudioTracks, AccurateRipVerify.CalculateAccurateRipId(toc)); + } + + internal static string CreateRequestBody(string accurateRipDiscId) + { + return JsonConvert.SerializeObject(new AccurateRipMetaRequest + { + accurateripdiscid = accurateRipDiscId + }); + } + + private static string Clean(JToken value) + { + if (value == null || value.Type == JTokenType.Null || value.Type == JTokenType.Array || value.Type == JTokenType.Object) + return ""; + + JValue scalar = value as JValue; + // The live contract uses strings for identifiers such as UPC. Numeric coercion is fail-soft only and cannot preserve leading zeroes. + return scalar == null || scalar.Value == null ? "" : Convert.ToString(scalar.Value, CultureInfo.InvariantCulture).Trim(); + } + + private static List OrderTracksByTrackNumber(JArray tracks, int audioTracks) + { + var byNumber = new Dictionary(); + foreach (JToken item in tracks) + { + JObject track = item as JObject; + if (track == null) + return null; + + int trackNumber; + if (!int.TryParse(Clean(track["TrackNumber"]), NumberStyles.Integer, CultureInfo.InvariantCulture, out trackNumber)) + return null; + + if (trackNumber < 1 || trackNumber > audioTracks) + continue; + + if (byNumber.ContainsKey(trackNumber)) + return null; + + byNumber.Add(trackNumber, track); + } + + if (byNumber.Count != audioTracks) + return null; + + return Enumerable.Range(1, audioTracks) + .Select(trackNumber => byNumber[trackNumber]) + .ToList(); + } + + private static string NormalizeCoverUrl(JToken value) + { + string cleaned = Clean(value); + if (cleaned == "") + return ""; + + Uri uri; + if (!Uri.TryCreate(cleaned, UriKind.Absolute, out uri)) + return ""; + + if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps) + return ""; + + return uri.AbsoluteUri; + } + + private static string FirstNonEmptyLine(JToken value) + { + string text = Clean(value); + if (string.IsNullOrWhiteSpace(text)) + return ""; + + return text + .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) + .Select(line => line.Trim()) + .FirstOrDefault(line => line.Length > 0) ?? ""; + } + + private static string NormalizeProviderList(JToken value) + { + string text = Clean(value); + if (string.IsNullOrWhiteSpace(text)) + return ""; + + return string.Join(" & ", text + .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) + .Select(line => line.Trim()) + .Where(line => line.Length > 0) + .Distinct() + .ToArray()); + } + } + + public sealed class AccurateRipMetaProvider + { + private readonly IAccurateRipMetaTransport transport; + + public AccurateRipMetaProvider() + : this(new AccurateRipMetaHttpTransport()) + { + } + + public AccurateRipMetaProvider(IAccurateRipMetaTransport transport) + { + this.transport = transport ?? throw new ArgumentNullException(nameof(transport)); + } + + public CUEMetadataEntry Lookup(CDImageLayout toc, IWebProxy proxy) + { + if (toc == null) + return null; + + try + { + string accurateRipDiscId = AccurateRipMeta.CreateRequestDiscId(toc); + string body = AccurateRipMeta.CreateRequestBody(accurateRipDiscId); + string json = transport.Post(AccurateRipMeta.Endpoint, body, proxy); + + return AccurateRipMeta.TryParseMetadata(json, toc, out CUEMetadataEntry entry) ? entry : null; + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine("AccurateRip Meta lookup failed: " + ex.Message); + return null; + } + } + } + + public interface IAccurateRipMetaTransport + { + string Post(string url, string body, IWebProxy proxy); + } + + public sealed class AccurateRipMetaHttpTransport : IAccurateRipMetaTransport + { + public string Post(string url, string body, IWebProxy proxy) + { + var request = (HttpWebRequest)WebRequest.Create(url); + request.Method = "POST"; + request.UserAgent = "Mozilla/5.0"; + request.ContentType = "application/x-www-form-urlencoded"; + request.AllowAutoRedirect = false; + request.Timeout = 15000; + request.ReadWriteTimeout = 30000; + if (proxy != null) + request.Proxy = proxy; + + byte[] bytes = Encoding.UTF8.GetBytes(body); + request.ContentLength = bytes.Length; + using (Stream requestStream = request.GetRequestStream()) + requestStream.Write(bytes, 0, bytes.Length); + + using (var response = (HttpWebResponse)request.GetResponse()) + using (Stream responseStream = response.GetResponseStream()) + using (var reader = new StreamReader(responseStream, Encoding.UTF8)) + return reader.ReadToEnd(); + } + } + + internal sealed class AccurateRipMetaRequest + { + public string accurateripdiscid { get; set; } + } +} +``` + +- [ ] **Step 2: Run mapper tests** + +Run: + +```powershell +dotnet test CUETools\CUETools.TestProcessor\CUETools.TestProcessor.csproj --filter "FullyQualifiedName~AccurateRipMetaTest.TryParseMetadata" -v:minimal +``` + +Expected: PASS for all ten mapper tests whose names start with `TryParseMetadata`. + +- [ ] **Step 3: Commit the mapper** + +```powershell +git add CUETools.Processor/AccurateRipMeta.cs +git commit -m "feat: map AccurateRip Meta responses" +``` + +--- + +### Task 3: Add Transport And Provider Tests + +**Files:** +- Modify: `CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs` +- Modify: `CUETools.Processor/AccurateRipMeta.cs` + +- [ ] **Step 1: Add failing provider tests** + +Append these tests and helper class to `AccurateRipMetaTest` before the final closing brace of the class: + +```csharp + [TestMethod] + public void LookupPostsSerializedAccurateRipIdAndUsesProxy() + { + var toc = CreateTwoTrackToc(); + var proxy = new WebProxy("127.0.0.1", 8888); + var transport = new FakeTransport { Response = SampleJson(trackCount: 2) }; + var provider = new AccurateRipMetaProvider(transport); + + CUEMetadataEntry entry = provider.Lookup(toc, proxy); + + Assert.IsNotNull(entry); + Assert.AreEqual(AccurateRipMeta.Endpoint, transport.Url); + Assert.AreSame(proxy, transport.Proxy); + var body = JsonConvert.DeserializeObject>(transport.Body); + Assert.AreEqual(string.Format("{0:000}-{1}", toc.AudioTracks, AccurateRipVerify.CalculateAccurateRipId(toc)), body["accurateripdiscid"]); + } + + [TestMethod] + public void LookupReturnsNullWhenTransportThrows() + { + var transport = new FakeTransport { Exception = new WebException("network unavailable") }; + var provider = new AccurateRipMetaProvider(transport); + + CUEMetadataEntry entry = provider.Lookup(CreateTwoTrackToc(), null); + + Assert.IsNull(entry); + } + + [TestMethod] + public void LookupReturnsNullForInvalidJson() + { + var transport = new FakeTransport { Response = "{not-json" }; + var provider = new AccurateRipMetaProvider(transport); + + CUEMetadataEntry entry = provider.Lookup(CreateTwoTrackToc(), null); + + Assert.IsNull(entry); + } + + private sealed class FakeTransport : IAccurateRipMetaTransport + { + public string Url { get; private set; } + public string Body { get; private set; } + public IWebProxy Proxy { get; private set; } + public int PostCount { get; private set; } + public string Response { get; set; } + public Exception Exception { get; set; } + + public string Post(string url, string body, IWebProxy proxy) + { + PostCount++; + Url = url; + Body = body; + Proxy = proxy; + + if (Exception != null) + throw Exception; + + return Response; + } + } +``` + +- [ ] **Step 2: Run tests to verify current implementation passes** + +Run: + +```powershell +dotnet test CUETools\CUETools.TestProcessor\CUETools.TestProcessor.csproj --filter AccurateRipMetaTest -v:minimal +``` + +Expected: PASS for all `AccurateRipMetaTest` tests. If this fails because Task 2 did not expose `IAccurateRipMetaTransport` publicly, make the interface public as shown in Task 2 and rerun. + +- [ ] **Step 3: Commit provider tests** + +```powershell +git add CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs CUETools.Processor/AccurateRipMeta.cs +git commit -m "test: cover AccurateRip Meta transport behavior" +``` + +--- + +### Task 4: Integrate AccurateRip Meta Into CUESheet Lookup + +**Files:** +- Modify: `CUETools.Processor/CUESheet.cs` +- Modify: `CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs` + +Behavior change to preserve: `CTDBMetadataSearch.Extensive` must query AccurateRip Meta even when CTDB has already returned metadata. The focused unit tests use a CTDB metadata seam to avoid network I/O while still proving the CTDB-succeeded branch. + +- [ ] **Step 1: Add failing CUESheet lookup tests** + +Append these tests to `AccurateRipMetaTest` before the `FakeTransport` class: + +```csharp + [TestMethod] + public void LookupAlbumInfoExtensiveAddsAccurateRipMeta() + { + var transport = new FakeTransport { Response = SampleJson(trackCount: 1) }; + var sheet = new CUESheet(new CUEConfig()) + { + AccurateRipMetaProvider = new AccurateRipMetaProvider(transport) + }; + sheet.Open("Amarok\\Amarok.cue"); + + var releases = sheet.LookupAlbumInfo(false, false, false, CUETools.CTDB.CTDBMetadataSearch.Extensive) + .OfType() + .ToList(); + + Assert.AreEqual(1, releases.Count(entry => entry.ImageKey == AccurateRipMeta.SourceKey)); + } + + [TestMethod] + public void LookupAlbumInfoExtensiveAddsAccurateRipMetaWhenCtdbReturnedMetadata() + { + var transport = new FakeTransport { Response = SampleJson(trackCount: 1) }; + var sheet = new CtdbStubCUESheet(new CUEConfig(), CreateCtdbMetadata()) + { + AccurateRipMetaProvider = new AccurateRipMetaProvider(transport) + }; + sheet.Open("Amarok\\Amarok.cue"); + + var releases = sheet.LookupAlbumInfo(false, false, true, CTDBMetadataSearch.Extensive) + .OfType() + .ToList(); + + Assert.AreEqual(1, releases.Count(entry => entry.ImageKey == "ctdb")); + Assert.AreEqual(1, releases.Count(entry => entry.ImageKey == AccurateRipMeta.SourceKey)); + Assert.AreEqual(1, transport.PostCount); + } + + [TestMethod] + public void LookupAlbumInfoDefaultDoesNotQueryAccurateRipMeta() + { + var transport = new FakeTransport { Response = SampleJson(trackCount: 1) }; + var sheet = new CUESheet(new CUEConfig()) + { + AccurateRipMetaProvider = new AccurateRipMetaProvider(transport) + }; + sheet.Open("Amarok\\Amarok.cue"); + + var releases = sheet.LookupAlbumInfo(false, false, false, CUETools.CTDB.CTDBMetadataSearch.Default) + .OfType() + .ToList(); + + Assert.AreEqual(0, releases.Count(entry => entry.ImageKey == AccurateRipMeta.SourceKey)); + Assert.IsNull(transport.Url); + } + + private sealed class CtdbStubCUESheet : CUESheet + { + private readonly IEnumerable ctdbMetadata; + + public CtdbStubCUESheet(CUEConfig config, IEnumerable ctdbMetadata) + : base(config) + { + this.ctdbMetadata = ctdbMetadata; + } + + protected override IEnumerable LookupCtdbMetadata(CTDBMetadataSearch metadataSearch) + { + return ctdbMetadata; + } + } + + private static IEnumerable CreateCtdbMetadata() + { + return new[] + { + new CTDBResponseMeta + { + source = "ctdb", + artist = "CTDB Artist", + album = "CTDB Album", + track = new[] + { + new CTDBResponseMetaTrack + { + name = "CTDB Track", + artist = "CTDB Artist" + } + } + } + }; + } +``` + +- [ ] **Step 2: Run tests to verify they fail for the expected reason** + +Run: + +```powershell +dotnet test CUETools\CUETools.TestProcessor\CUETools.TestProcessor.csproj --filter "FullyQualifiedName~LookupAlbumInfo" -v:minimal +``` + +Expected: FAIL at compile time because `CUESheet.AccurateRipMetaProvider` does not exist, or FAIL at runtime because `LookupAlbumInfo` does not add AccurateRip Meta. + +- [ ] **Step 3: Add provider property and CTDB metadata seam in `CUESheet`** + +In `CUETools.Processor/CUESheet.cs`, replace the active `using Freedb;` line with a dormant-only using: + +```csharp +#if DORMANT_FREEDB +using Freedb; +#endif +``` + +Add this property near the existing public properties: + +```csharp + public AccurateRipMetaProvider AccurateRipMetaProvider { get; set; } +``` + +In the `CUESheet(CUEConfig config)` constructor, add this assignment after `_config = config;`: + +```csharp + AccurateRipMetaProvider = new AccurateRipMetaProvider(); +``` + +Add this protected helper near `LookupAlbumInfo` so tests can simulate CTDB metadata without network I/O: + +```csharp + protected virtual IEnumerable LookupCtdbMetadata(CTDBMetadataSearch metadataSearch) + { + var ctdb = new CUEToolsDB(TOC, proxy); + ctdb.ContactDB(_config.advanced.CTDBServer, "CUETools " + CUEToolsVersion, null, false, false, metadataSearch); + return ctdb.Metadata; + } +``` + +- [ ] **Step 4: Replace active FreeDB fallback in `LookupAlbumInfo`** + +In the active CTDB section of `LookupAlbumInfo`, replace the direct `CUEToolsDB` construction and contact: + +```csharp + var ctdb = new CUEToolsDB(TOC, proxy); + ctdb.ContactDB(_config.advanced.CTDBServer, "CUETools " + CUEToolsVersion, null, false, false, metadataSearch); + foreach (var meta in ctdb.Metadata) +``` + +with: + +```csharp + foreach (var meta in LookupCtdbMetadata(metadataSearch)) +``` + +Replace the current block starting with: + +```csharp + if (!ctdbFound && metadataSearch == CTDBMetadataSearch.Extensive) + { + ShowProgress("Looking up album via Freedb...", 0.0, null, null); +``` + +and ending just before: + +```csharp + ShowProgress("", 0, null, null); +``` + +with: + +```csharp + if (metadataSearch == CTDBMetadataSearch.Extensive) + { + ShowProgress("Looking up album via AccurateRip Meta...", 0.0, null, null); + CheckStop(); + + AccurateRipMetaProvider provider = AccurateRipMetaProvider ?? new AccurateRipMetaProvider(); + CUEMetadataEntry accurateRipMetaEntry = provider.Lookup(TOC, proxy); + if (accurateRipMetaEntry != null) + Releases.Add(accurateRipMetaEntry); + } +``` + +- [ ] **Step 5: Keep old FreeDB lookup dormant** + +Move the complete FreeDB lookup block removed in Step 4 to immediately above `ShowProgress("", 0, null, null);`. Put `#if DORMANT_FREEDB` immediately before the moved `if (!ctdbFound && metadataSearch == CTDBMetadataSearch.Extensive)` line and `#endif` immediately after the moved block's closing brace. Add this comment as the first line inside the dormant region: + +```csharp + // FreeDB lookup is dormant. AccurateRip Meta is the active replacement provider. +``` + +Keep the exact removed FreeDB code inside the wrapper. Do not leave an active `FreedbHelper`, `QueryResult`, `QueryResultCollection`, or `CDEntry` reference outside the wrapper. + +- [ ] **Step 6: Wrap `TocFromCDEntry` as dormant** + +In `CUETools.Processor/CUESheet.cs`, place `#if DORMANT_FREEDB` immediately before the current `public CDImageLayout TocFromCDEntry(CDEntry cdEntry)` declaration and `#endif` immediately after that method's closing brace. Add this comment as the first line inside the dormant region: + +```csharp + // FreeDB TOC reconstruction is dormant with the FreeDB provider. +``` + +Keep the existing `TocFromCDEntry` method body unchanged inside that dormant region. + +- [ ] **Step 7: Run CUESheet lookup tests** + +Run: + +```powershell +dotnet test CUETools\CUETools.TestProcessor\CUETools.TestProcessor.csproj --filter "FullyQualifiedName~LookupAlbumInfo" -v:minimal +``` + +Expected: PASS for both CUESheet lookup tests. + +- [ ] **Step 8: Commit CUESheet integration** + +```powershell +git add CUETools.Processor/CUESheet.cs CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs +git commit -m "feat: use AccurateRip Meta in processor lookup" +``` + +--- + +### Task 5: Isolate FreeDB Metadata Helpers + +**Files:** +- Modify: `CUETools.Processor/CUEMetadata.cs` +- Test: `CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs` + +Tasks 5, 6, and 7 form one atomic build-safe implementation group. Do not commit, hand off, or intentionally stop after Task 5 or Task 6, because the helper methods are removed from the active processor API before all active UI callers are wrapped. Commit the group only after Task 7 builds CUERipper. + +- [ ] **Step 1: Move FreeDB-specific helpers behind a dormant compile symbol** + +In `CUETools.Processor/CUEMetadata.cs`, create two separate `#if DORMANT_FREEDB` regions so the active `FillFromCtdb` method remains compiled: + +- Region 1 starts immediately before the current `public void FillFromFreedb(Freedb.CDEntry cdEntry, int firstAudio)` declaration and ends immediately after that method's closing brace. +- Region 2 starts immediately before the current `private static string FreedbToEncoding(Encoding iso, Encoding def, ref bool changed, ref bool error, string s)` declaration and ends immediately after the current `public bool FreedbToVarious()` method's closing brace. + +Add this comment as the first line inside both dormant regions: + +```csharp + // FreeDB metadata import and repair helpers are dormant. AccurateRip Meta is the active replacement provider. +``` + +The dormant regions together must contain these existing declarations with their current bodies unchanged: + +```csharp + public void FillFromFreedb(Freedb.CDEntry cdEntry, int firstAudio) + private static string FreedbToEncoding(Encoding iso, Encoding def, ref bool changed, ref bool error, string s) + public bool FreedbToEncoding() + public bool FreedbToVarious() +``` + +Do not include `public void FillFromCtdb(CUETools.CTDB.CTDBResponseMeta cdEntry, int firstAudio)` in either dormant region. `CUESheet.LookupAlbumInfo` and CUERipper still call `FillFromCtdb` in the active CTDB metadata path. + +- [ ] **Step 2: Continue without committing** + +Run this focused test only to catch accidental mapper regressions while the broader working tree is temporarily between caller updates: + +```powershell +dotnet test CUETools\CUETools.TestProcessor\CUETools.TestProcessor.csproj --filter AccurateRipMetaTest -v:minimal +``` + +Expected: PASS. Do not run or claim a whole-solution build yet; CUETools and CUERipper callers are updated in Tasks 6 and 7 before the commit. + +- [ ] **Step 3: Do not commit yet** + +Expected: no commit. Keep `CUETools.Processor/CUEMetadata.cs` in the working tree and continue directly to Task 6. + +--- + +### Task 6: Update CUETools Metadata Chooser UI + +**Files:** +- Modify: `CUETools/frmChoice.cs` + +- [ ] **Step 1: Register an AccurateRip Meta image key** + +Change the `frmChoice` constructor from: + +```csharp + public frmChoice() + { + InitializeComponent(); + } +``` + +to: + +```csharp + public frmChoice() + { + InitializeComponent(); + if (!imageList1.Images.ContainsKey(AccurateRipMeta.SourceKey)) + using (Image accurateRipIcon = Properties.Resources.accuraterip16) + imageList1.Images.Add(AccurateRipMeta.SourceKey, accurateRipIcon); + } +``` + +- [ ] **Step 2: Make FreeDB duplicate insertion dormant** + +In `AddItem`, replace this active block: + +```csharp + if (entry.ImageKey == "freedb") + { + // check if the entry contains non-iso characters, + // and add a second one if it does + CUEMetadata copy = new CUEMetadata(entry.metadata); + if (copy.FreedbToEncoding()) + { + entry = new CUEMetadataEntry(copy, entry.TOC, entry.ImageKey); + item = new ListViewItem(entry.ToString(), entry.ImageKey); + item.Tag = entry; + listChoices.Items.Add(item); + } + } +``` + +with: + +```csharp +#if DORMANT_FREEDB + // FreeDB encoding repair is dormant. AccurateRip Meta entries are already Unicode JSON. + if (entry.ImageKey == "freedb") + { + CUEMetadata copy = new CUEMetadata(entry.metadata); + if (copy.FreedbToEncoding()) + { + entry = new CUEMetadataEntry(copy, entry.TOC, entry.ImageKey); + item = new ListViewItem(entry.ToString(), entry.ImageKey); + item.Tag = entry; + listChoices.Items.Add(item); + } + } +#endif +``` + +- [ ] **Step 3: Build CUETools project** + +Run: + +```powershell +dotnet build CUETools\CUETools.csproj -v:minimal '/clp:ErrorsOnly;Summary' +``` + +Expected: Build succeeds. `frmChoice` should compile without any active FreeDB helper calls. + +- [ ] **Step 4: Do not commit yet** + +Expected: no commit. Keep `CUETools/frmChoice.cs` in the working tree and continue directly to Task 7. + +--- + +### Task 7: Update CUERipper Lookup And UI + +**Files:** +- Modify: `CUERipper/frmCUERipper.cs` +- Modify: `CUERipper/Options.cs` + +- [ ] **Step 1: Make FreeDB using dormant and add LINQ** + +At the top of `CUERipper/frmCUERipper.cs`, replace: + +```csharp +using Freedb; +``` + +with: + +```csharp +#if DORMANT_FREEDB +using Freedb; +#endif +``` + +Also add: + +```csharp +using System.Linq; +``` + +- [ ] **Step 2: Register an AccurateRip Meta image key** + +In the `frmCUERipper` constructor, immediately after `InitializeComponent();`, add: + +```csharp + if (!imageListMetadataSource.Images.ContainsKey(AccurateRipMeta.SourceKey)) + using (Image accurateRipIcon = Properties.Resources.accuraterip16) + imageListMetadataSource.Images.Add(AccurateRipMeta.SourceKey, accurateRipIcon); +``` + +- [ ] **Step 3: Hide FreeDB-only controls in `SetupControls`** + +Replace these lines in `SetupControls`: + +```csharp + buttonFreedbSubmit.Enabled = data.selectedRelease != null && !running; + buttonVA.Enabled = data.selectedRelease != null && !running && + data.selectedRelease.ImageKey == "freedb" && !data.selectedRelease.metadata.IsVarious() && (new CUEMetadata(data.selectedRelease.metadata)).FreedbToVarious(); + buttonEncoding.Enabled = data.selectedRelease != null && !running && + data.selectedRelease.ImageKey == "freedb" && (new CUEMetadata(data.selectedRelease.metadata)).FreedbToEncoding(); +``` + +with: + +```csharp + buttonFreedbSubmit.Visible = false; + buttonFreedbSubmit.Enabled = false; + buttonVA.Visible = false; + buttonVA.Enabled = false; + buttonEncoding.Visible = false; + buttonEncoding.Enabled = false; +``` + +- [ ] **Step 4: Add an AccurateRip Meta progress helper** + +Add this active helper at the current `FreeDB_LookupProgress` location: + +```csharp + private void AccurateRipMetaLookupProgress(string detail) + { + CheckStop(); + string text = Properties.Resources.LookingUpVia + " " + AccurateRipMeta.DisplayName + "..." + (string.IsNullOrEmpty(detail) ? "" : " " + detail); + this.BeginInvoke((MethodInvoker)delegate() + { + toolStripStatusLabel1.Text = text; + toolStripProgressBar1.Value = (100 + 2 * toolStripProgressBar1.Value) / 3; + }); + } +``` + +Preserve the old `FreeDB_LookupProgress` body under `#if DORMANT_FREEDB`, because the dormant FreeDB lookup block still calls it when the dormant symbol is enabled. Put `#if DORMANT_FREEDB` immediately before its current declaration and `#endif` immediately after that method's closing brace. Add this comment as the first line inside the dormant region: + +```csharp + // FreeDB progress UI is dormant with the FreeDB provider. +``` + +- [ ] **Step 5: Replace CUERipper FreeDB lookup block** + +Keep this replacement inside the existing `if (data.Releases.Count == 0 || loadAllMetadata)` gate. CUERipper intentionally preserves the current fallback/reload lookup semantics: it does not issue an AccurateRip Meta request on ordinary lookups where CTDB already populated `data.Releases`, but reload/all-metadata still queries it even when CTDB returned entries. + +In `Lookup`, replace the active block from: + +```csharp + this.BeginInvoke((MethodInvoker)delegate() { toolStripStatusLabel1.Text = Properties.Resources.LookingUpVia + " Freedb..."; }); + + FreedbHelper m_freedb = new FreedbHelper(); +``` + +through the end of the FreeDB `try/catch` block with: + +```csharp + AccurateRipMetaLookupProgress(null); + try + { + var provider = new AccurateRipMetaProvider(); + CUEMetadataEntry accurateRipMeta = provider.Lookup(audioSource.TOC, _config.GetProxy()); + if (accurateRipMeta != null && !data.Releases.Any(r => + r.ImageKey == AccurateRipMeta.SourceKey && + r.metadata.Contains(accurateRipMeta.metadata))) + { + data.Releases.Add(accurateRipMeta); + } + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine(ex.Message); + } +``` + +The duplicate check is intentionally narrow: it suppresses repeated AccurateRip Meta entries on reload, but it does not hide an AccurateRip Meta release just because an existing CTDB/local release has the same metadata text. + +Move the complete removed FreeDB lookup block immediately below the new AccurateRip Meta block. Put `#if DORMANT_FREEDB` immediately before the moved block and `#endif` immediately after it. Add this comment as the first line inside the dormant region: + +```csharp + // FreeDB lookup is dormant. AccurateRip Meta is the active replacement provider. +``` + +- [ ] **Step 6: Fetch every release's artwork before CTDB-only artwork scan** + +Replace the artwork worker launch: + +```csharp + backgroundWorkerArtwork.RunWorkerAsync(new BackgroundWorkerArtworkArgs() { cueSheet = cueSheet, meta = data.selectedRelease }); +``` + +with: + +```csharp + backgroundWorkerArtwork.RunWorkerAsync(new BackgroundWorkerArtworkArgs() + { + cueSheet = cueSheet, + meta = data.selectedRelease, + releases = data.Releases.ToList() + }); +``` + +Replace the full `backgroundWorkerArtwork_DoWork` method in `CUERipper/frmCUERipper.cs` with: + +```csharp + private void backgroundWorkerArtwork_DoWork(object sender, DoWorkEventArgs e) + { + var args = e.Argument as BackgroundWorkerArtworkArgs; + var cueSheet = args.cueSheet; + albumArt.Clear(); + currentAlbumArt = 0; + var knownUrls = new List(); + var firstUrls = new List(); + var releaseCovers = new List(); + + var releases = args.releases ?? new List(); + if (releases.Count == 0 && args.meta != null) + releases.Add(args.meta); + + foreach (var release in releases) + { + if (release == null || release.metadata == null || release.metadata.AlbumArt == null) + continue; + + foreach (var releaseCover in release.metadata.AlbumArt) + { + if (releaseCover == null) + continue; + + releaseCovers.Add(releaseCover); + if (object.ReferenceEquals(release, args.meta) && !string.IsNullOrEmpty(releaseCover.uri)) + firstUrls.Add(releaseCover.uri); + } + } + + foreach (var releaseCover in releaseCovers) + { + string fetchUrl = !string.IsNullOrEmpty(releaseCover.uri150) ? releaseCover.uri150 : releaseCover.uri; + if (string.IsNullOrEmpty(fetchUrl) || knownUrls.Contains(fetchUrl)) + continue; + + var ms = new MemoryStream(); + if (!cueSheet.CTDB.FetchFile(fetchUrl, ms)) + continue; + + lock (this.albumArt) + { + if (backgroundWorkerArtwork.CancellationPending) + { + e.Cancel = true; + return; + } + this.albumArt.Add(new AlbumArt(releaseCover, ms.ToArray())); + } + knownUrls.Add(fetchUrl); + backgroundWorkerArtwork.ReportProgress(0); + } + + for (int i = 0; i < 2; i++) + { + foreach (var metadata in cueSheet.CTDB.Metadata) + { + if (metadata.coverart == null) + continue; + foreach (var coverart in metadata.coverart) + { + var uri = coverart.uri150; + if (knownUrls.Contains(uri) || + (_config.advanced.coversSearch == CUEConfigAdvanced.CTDBCoversSearch.Primary && !coverart.primary)) + continue; + if (i == 0 && !firstUrls.Contains(coverart.uri)) + continue; + var ms = new MemoryStream(); + if (!cueSheet.CTDB.FetchFile(uri, ms)) + continue; + lock (this.albumArt) + { + if (backgroundWorkerArtwork.CancellationPending) + { + e.Cancel = true; + return; + } + this.albumArt.Add(new AlbumArt(coverart, ms.ToArray())); + } + knownUrls.Add(uri); + backgroundWorkerArtwork.ReportProgress(0); + } + } + } + } +``` + +Replace `BackgroundWorkerArtworkArgs` with: + +```csharp + internal class BackgroundWorkerArtworkArgs + { + public CUESheet cueSheet; + public CUEMetadataEntry meta; + public List releases; + } +``` + +- [ ] **Step 7: Make `CreateCUESheet(ICDRipper, CDEntry)` dormant** + +In `CUERipper/frmCUERipper.cs`, place `#if DORMANT_FREEDB` immediately before the current `private CUEMetadataEntry CreateCUESheet(ICDRipper audioSource, CDEntry cdEntry)` declaration and `#endif` immediately after that method's closing brace. Add this comment as the first line inside the dormant region: + +```csharp + // FreeDB CDEntry conversion is dormant with the FreeDB provider. +``` + +Keep the existing overload body unchanged inside that dormant region. + +- [ ] **Step 8: Keep designer-required FreeDB button handlers as stubs** + +Replace `buttonVA_Click`, `buttonEncoding_Click`, and `buttonFreedbSubmit_Click` bodies with non-dormant stubs: + +```csharp + private void buttonVA_Click(object sender, EventArgs e) + { + // FreeDB various-artist repair is dormant. AccurateRip Meta does not need this action. + } + + private void buttonEncoding_Click(object sender, EventArgs e) + { + // FreeDB encoding repair is dormant. AccurateRip Meta responses are Unicode JSON. + } + + private void buttonFreedbSubmit_Click(object sender, EventArgs e) + { + // FreeDB submission is dormant. AccurateRip Meta currently has no submission API. + } +``` + +Move the old bodies below the stubs under `#if DORMANT_FREEDB` with their original method names changed to avoid duplicate definitions. The old `buttonVA_Click` body moves into `DormantFreedbVariousArtistRepair`, the old `buttonEncoding_Click` body moves into `DormantFreedbEncodingRepair`, and the existing `FreedbSubmit(object o)` body stays under the same dormant region. + +- [ ] **Step 9: Remove FreeDB setting from normal CUERipper options** + +In `CUERipper/Options.cs`, replace: + +```csharp + [DefaultValue("gnudb.gnudb.org"), Category("Various"), DisplayName("Freedb site address")] + public string FreedbSiteAddress { get { return config.advanced.FreedbSiteAddress ; } set { config.advanced.FreedbSiteAddress = value; } } +``` + +with: + +```csharp +#if DORMANT_FREEDB + [DefaultValue("gnudb.gnudb.org"), Category("Various"), DisplayName("Freedb site address")] + public string FreedbSiteAddress { get { return config.advanced.FreedbSiteAddress ; } set { config.advanced.FreedbSiteAddress = value; } } +#endif +``` + +- [ ] **Step 10: Build CUERipper project** + +Run: + +```powershell +dotnet build CUERipper\CUERipper.csproj -v:minimal '/clp:ErrorsOnly;Summary' +``` + +Expected: Build succeeds. No active `Freedb` namespace references should remain in `frmCUERipper.cs`. + +- [ ] **Step 11: Commit CUERipper integration** + +```powershell +git add CUETools.Processor/CUEMetadata.cs CUETools/frmChoice.cs CUERipper/frmCUERipper.cs CUERipper/Options.cs +git commit -m "feat: make FreeDB UI and helpers dormant" +``` + +--- + +### Task 8: Hide FreeDB Advanced Settings + +**Files:** +- Modify: `CUETools.Processor/CUEConfigAdvanced.cs` +- Inspect: `CUETools/frmSettings.cs:43-48` +- Test: `CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs` + +- [ ] **Step 1: Add a failing settings exposure test** + +Append this test to `AccurateRipMetaTest` before the `FakeTransport` class: + +```csharp + [TestMethod] + public void CUEConfigAdvancedDoesNotExposeFreedbSettings() + { + var freedbProperties = TypeDescriptor.GetProperties(new CUEConfigAdvanced()) + .Cast() + .Where(property => property.Category == "Freedb") + .Select(property => property.Name) + .ToList(); + + CollectionAssert.AreEqual(new string[0], freedbProperties); + } +``` + +`System.ComponentModel` was added to the test file in Task 1 so `TypeDescriptor` and `PropertyDescriptor` resolve. + +- [ ] **Step 2: Run the settings exposure test to verify it fails** + +Run: + +```powershell +dotnet test CUETools\CUETools.TestProcessor\CUETools.TestProcessor.csproj --filter CUEConfigAdvancedDoesNotExposeFreedbSettings -v:minimal +``` + +Expected: FAIL because `CUEConfigAdvanced` still exposes `FreedbUser`, `FreedbDomain`, and `FreedbSiteAddress` with `Category("Freedb")`. + +- [ ] **Step 3: Make FreeDB advanced settings dormant** + +In `CUETools.Processor/CUEConfigAdvanced.cs`, replace the active FreeDB settings properties: + +```csharp + [DefaultValue("i"), Category("Freedb"), DisplayName("Email user")] + public string FreedbUser { get; set; } + + [DefaultValue("wont.tell"), Category("Freedb"), DisplayName("Email domain")] + public string FreedbDomain { get; set; } + + [DefaultValue("gnudb.gnudb.org"), Category("Freedb"), DisplayName("Site address")] + public string FreedbSiteAddress { get; set; } +``` + +with: + +```csharp +#if DORMANT_FREEDB + [DefaultValue("i"), Category("Freedb"), DisplayName("Email user")] + public string FreedbUser { get; set; } + + [DefaultValue("wont.tell"), Category("Freedb"), DisplayName("Email domain")] + public string FreedbDomain { get; set; } + + [DefaultValue("gnudb.gnudb.org"), Category("Freedb"), DisplayName("Site address")] + public string FreedbSiteAddress { get; set; } +#endif +``` + +- [ ] **Step 4: Confirm CUETools settings UI now follows the hidden properties** + +Run: + +```powershell +rg -n 'propertyGrid1.SelectedObject = _config\.advanced|Category\("Freedb"\)|FreedbUser|FreedbDomain|FreedbSiteAddress' CUETools\frmSettings.cs CUETools.Processor\CUEConfigAdvanced.cs +``` + +Expected: `CUETools/frmSettings.cs` still has `propertyGrid1.SelectedObject = _config.advanced`; FreeDB properties and `Category("Freedb")` remain only inside the `#if DORMANT_FREEDB` region in `CUEConfigAdvanced.cs`. + +- [ ] **Step 5: Run the settings exposure test** + +Run: + +```powershell +dotnet test CUETools\CUETools.TestProcessor\CUETools.TestProcessor.csproj --filter CUEConfigAdvancedDoesNotExposeFreedbSettings -v:minimal +``` + +Expected: PASS. + +- [ ] **Step 6: Commit FreeDB settings dormancy** + +```powershell +git add CUETools.Processor/CUEConfigAdvanced.cs CUETools/CUETools.TestProcessor/AccurateRipMetaTest.cs +git commit -m "refactor: make FreeDB settings dormant" +``` + +--- + +### Task 9: Remove Active FreeDB Project References + +**Files:** +- Modify: `CUETools.Processor/CUETools.Processor.csproj` +- Modify: `CUETools/CUETools.csproj` +- Modify: `CUERipper/CUERipper.csproj` +- Modify: `CUETools/CUETools.TestProcessor/CUETools.TestProcessor.csproj` +- Modify: `CUETools/CUETools.TestCodecs/CUETools.TestCodecs.csproj` +- Modify: `CUETools.sln` + +- [ ] **Step 1: Confirm no active source references FreeDB** + +Run: + +```powershell +rg -n "using Freedb|FreedbHelper|FillFromFreedb|FreedbToEncoding|FreedbToVarious|FreedbUser|FreedbDomain|FreedbSiteAddress|Category\(\"Freedb\"\)|CDEntry|QueryResult" CUETools.Processor CUETools CUERipper CUETools\CUETools.TestProcessor CUETools\CUETools.TestCodecs -g "*.cs" +``` + +Expected: Matches only inside `#if DORMANT_FREEDB` regions, comments, dormant forms, or resource/designer artifacts. No normal active code path should require the `Freedb` project or expose FreeDB settings. + +- [ ] **Step 2: Remove FreeDB project references** + +Remove these XML lines: + +From `CUETools.Processor/CUETools.Processor.csproj`: + +```xml + +``` + +From `CUETools/CUETools.csproj`: + +```xml + +``` + +From `CUERipper/CUERipper.csproj`: + +```xml + +``` + +From `CUETools/CUETools.TestProcessor/CUETools.TestProcessor.csproj`: + +```xml + +``` + +From `CUETools/CUETools.TestCodecs/CUETools.TestCodecs.csproj`: + +```xml + +``` + +- [ ] **Step 3: Remove FreeDB project from solution** + +Run: + +```powershell +dotnet sln CUETools.sln remove Freedb\Freedb.csproj +``` + +Expected: command reports that `Freedb\Freedb.csproj` was removed from the solution. + +- [ ] **Step 4: Check for remaining solution-level FreeDB references** + +Run: + +```powershell +rg -n "Freedb|freedb" CUETools.sln +``` + +Expected: no active project reference remains. If `ThirdParty\Freedb.dll` remains as a solution item, remove that line from `CUETools.sln` because the active solution no longer ships FreeDB as a provider. + +- [ ] **Step 5: Build affected projects** + +Run: + +```powershell +dotnet build CUETools.Processor\CUETools.Processor.csproj -v:minimal '/clp:ErrorsOnly;Summary' +dotnet build CUETools\CUETools.csproj -v:minimal '/clp:ErrorsOnly;Summary' +dotnet build CUERipper\CUERipper.csproj -v:minimal '/clp:ErrorsOnly;Summary' +dotnet build CUETools\CUETools.TestCodecs\CUETools.TestCodecs.csproj -v:minimal '/clp:ErrorsOnly;Summary' +dotnet test CUETools\CUETools.TestProcessor\CUETools.TestProcessor.csproj --filter AccurateRipMetaTest -v:minimal +``` + +Expected: all commands succeed. + +- [ ] **Step 6: Commit dependency cleanup** + +```powershell +git add CUETools.Processor/CUETools.Processor.csproj CUETools/CUETools.csproj CUERipper/CUERipper.csproj CUETools/CUETools.TestProcessor/CUETools.TestProcessor.csproj CUETools/CUETools.TestCodecs/CUETools.TestCodecs.csproj CUETools.sln +git commit -m "refactor: remove active FreeDB dependencies" +``` + +--- + +### Task 10: Final Verification + +**Files:** +- No planned source edits + +- [ ] **Step 1: Run focused tests** + +Run: + +```powershell +dotnet test CUETools\CUETools.TestProcessor\CUETools.TestProcessor.csproj --filter AccurateRipMetaTest -v:minimal +``` + +Expected: all AccurateRip Meta tests pass. + +- [ ] **Step 2: Run solution inventory** + +Run: + +```powershell +dotnet sln CUETools.sln list +``` + +Expected: the solution lists `dotnet10-update` projects and no `Freedb\Freedb.csproj` entry. + +- [ ] **Step 3: Build solution** + +Run: + +```powershell +dotnet build CUETools.sln -v:minimal '/clp:ErrorsOnly;Summary' +``` + +Expected: solution build succeeds. If it fails before reaching this feature because clean third-party submodules are missing CUETools patches, apply the documented parent patches temporarily: + +```powershell +git apply ThirdParty/submodule_taglib-sharp_CUETools.patch +git apply ThirdParty/submodule_openclnet_CUETools.patch +git apply ThirdParty/submodule_WindowsMediaLib_CUETools.patch +dotnet build CUETools.sln -v:minimal '/clp:ErrorsOnly;Summary' +git apply -R ThirdParty/submodule_WindowsMediaLib_CUETools.patch +git apply -R ThirdParty/submodule_openclnet_CUETools.patch +git apply -R ThirdParty/submodule_taglib-sharp_CUETools.patch +``` + +Expected after temporary patches: build succeeds, and `git status --short` does not show retained third-party patch changes. + +- [ ] **Step 4: Run solution tests without rebuilding** + +Run: + +```powershell +dotnet test CUETools.sln --no-build -v:minimal +``` + +Expected: all runnable tests pass; skipped tests remain skipped. + +- [ ] **Step 5: Verify FreeDB is dormant** + +Run: + +```powershell +rg -n "Looking up album via Freedb|Submit to freedb|Freedb site address|Category\(\"Freedb\"\)|FreedbUser|FreedbDomain|FreedbSiteAddress|ImageKey == \"freedb\"|using Freedb|FreedbHelper" CUETools.Processor CUETools CUERipper -g "*.cs" -g "*.resx" +``` + +Expected: remaining matches are either in `#if DORMANT_FREEDB` code, comments, resource text for dormant forms, or files deliberately retained for possible future reactivation. No normal runtime path should display or call FreeDB, and no normal property grid should expose FreeDB settings. + +- [ ] **Step 6: Verify FreeDB submit form is orphaned from normal UI** + +Run: + +```powershell +rg -n "new frmFreedbSubmit|frmFreedbSubmit|buttonFreedbSubmit_Click|FreedbSubmit\(" CUERipper -g "*.cs" -g "*.Designer.cs" -g "*.resx" +``` + +Expected: `frmFreedbSubmit` form/designer/resource files still exist, and the only construction path is inside the dormant submit code. Normal event-handler stubs must not create or show `frmFreedbSubmit`. + +- [ ] **Step 7: Verify no active FreeDB project references remain** + +Run: + +```powershell +rg -n "Freedb\.csproj" -g "*.csproj" -g "*.sln" +``` + +Expected: no matches. + +- [ ] **Step 8: Run manual smoke checks** + +Run CUETools and CUERipper from the built output and check these behaviors with a disc or image that can return AccurateRip Meta data: + +- CUETools metadata chooser shows `AccurateRip Meta` as a selectable source when the provider returns metadata. +- CUETools `CTDBMetadataSearch.Extensive` lookup still posts to AccurateRip Meta when CTDB has already returned metadata. +- CUETools metadata chooser shows cover preview when the provider returns a valid `_arturl`. +- CUERipper ordinary lookup preserves fallback semantics: if CTDB already populated releases, AccurateRip Meta is not queried until reload/all-metadata is requested. +- CUERipper reload/all-metadata lookup can show an `AccurateRip Meta` release. +- CUERipper artwork preview downloads the AccurateRip Meta cover URL when it is initially selected and when the user selects it later from a non-default release entry. +- CUERipper toolbar/control layout remains acceptable with the FreeDB submit/repair buttons hidden. +- CUERipper lookup progress text uses the localized `LookingUpVia` prefix and the `AccurateRip Meta` provider suffix in the default UI and, if available on the test machine, de-DE or ru-RU resources. +- FreeDB submit/settings are not reachable in normal CUETools or CUERipper UI. + +Expected: all nine checks pass. + +- [ ] **Step 9: Commit final verification cleanup if needed** + +Run: + +```powershell +git status --short +``` + +Expected: no uncommitted files from final verification. If this command lists cleanup files changed by the final verification task, stage exactly those listed cleanup files and commit them with: + +```powershell +git commit -m "chore: finish AccurateRip Meta verification cleanup" +``` + +If no files changed during verification, do not create a commit. diff --git a/docs/superpowers/specs/2026-05-27-accuraterip-meta-design.md b/docs/superpowers/specs/2026-05-27-accuraterip-meta-design.md new file mode 100644 index 000000000..8f2ee6584 --- /dev/null +++ b/docs/superpowers/specs/2026-05-27-accuraterip-meta-design.md @@ -0,0 +1,111 @@ +# AccurateRip Meta Replacement Design + +## Goal + +Replace CUETools' active FreeDB metadata implementation with AccurateRip Meta while leaving other metadata providers unchanged. The replacement should appear to users as `AccurateRip Meta`, provide cover art through the existing album-art flow, and avoid copying any API key or secret from the EasyEACGUI sample project. + +Implementation work must happen on a feature branch based on `dotnet10-update` so the .NET 10 migration changes are included. + +## Scope + +In scope: + +- Add a first-party AccurateRip Meta lookup path in `CUETools.Processor`. +- Replace active FreeDB lookup behavior in CUETools and CUERipper. +- Show AccurateRip Meta as a selectable metadata source in the UI. +- Preserve cover URLs from AccurateRip Meta using the existing `CUEMetadata.AlbumArt` model. +- Hide or disable FreeDB submit/settings UI in normal runtime. +- Mark old FreeDB-specific code sections as dormant and deletion/adaptation candidates. + +Out of scope: + +- Changing CTDB, MusicBrainz, local cache, CUE, tag, or AccurateRip verification behavior. +- Adding AccurateRip Meta submission support, because the provider currently does not support submissions. +- Adding new persistent metadata fields for provider values that do not have clear CUETools storage targets. +- Copying auxiliary EasyEACGUI infrastructure wholesale. + +## Architecture + +Add a small AccurateRip Meta client/mapper in `CUETools.Processor`, near the existing metadata lookup orchestration. The client calculates the provider id with `AccurateRipVerify.CalculateAccurateRipId(toc)`, posts that id to `http://meta.accuraterip.com/discmatch`, deserializes the JSON response with the existing `Newtonsoft.Json` dependency, and returns a `CUEMetadataEntry` with source key `accurateripmeta`. + +`CUESheet.LookupAlbumInfo` will keep the current local/CUE/tag/CTDB flow, but `CTDBMetadataSearch.Extensive` will also query AccurateRip Meta and add its result as an additional selectable entry, even if CTDB already returned metadata. This differs from the current FreeDB fallback, which only runs if CTDB found nothing. + +CUERipper will use the same processor-level provider for no-result fallback and explicit reload/all-metadata lookup. Active FreeDB calls will be replaced by AccurateRip Meta calls. FreeDB submit/config code will remain dormant and clearly marked rather than physically removed in the first pass. + +## Data Mapping + +Map AccurateRip Meta fields into the existing CUETools metadata model: + +- `Artist` -> `CUEMetadata.Artist` +- `Album` -> `CUEMetadata.Title` +- `Year` -> `CUEMetadata.Year` +- distinct non-empty `Genre` lines joined with `; ` -> `CUEMetadata.Genre` +- `UPC` -> `CUEMetadata.Barcode` +- `Label` -> `CUEMetadata.Label` +- `CatalogNum` -> `CUEMetadata.LabelNo` +- `tracks[].Title` -> `CUETrackMetadata.Title` +- `tracks[].Artist` -> `CUETrackMetadata.Artist` +- `tracks[].ISRC` -> `CUETrackMetadata.ISRC` +- `_arturl` -> `CUEMetadata.AlbumArt` + +`CatalogNum` maps to `LabelNo` because CUETools already uses `LabelNo` as the catalog number slot: it is displayed as `Label#`, read from cue `REM CATALOGNUMBER`, and written to TagLib `CatalogNo`. + +Cover art will be represented as a `CTDBResponseMetaImage` with `uri` and `uri150` set to `_arturl`, `primary = true`, and dimensions left at `0` when the provider does not supply dimensions. `_arturl` is accepted only from `meta.accuraterip.com`; URLs from other hosts are dropped to avoid making CUETools fetch provider-supplied third-party URLs. This lets the existing CUETools and CUERipper cover preview/download paths continue to work for the observed live provider responses. + +The AccurateRip Meta response also exposes `Styles`, `tracks[].Composers`, and `tracks[].Conductors`. These fields are available from the provider but intentionally unused until CUETools has explicit fields for them. Add a short code comment near the mapping to document that decision. + +## UI Behavior + +User-facing source text should be `AccurateRip Meta`. Internal source identity should use a new key such as `accurateripmeta`; do not reuse `freedb` for new results. + +CUETools metadata chooser: + +- Shows AccurateRip Meta entries as selectable releases. +- Uses the existing cover preview path when `_arturl` is present. +- Uses an existing AccurateRip icon where practical, or another existing resource if resource churn would be disproportionate. +- Does not offer FreeDB-specific encoding repair for AccurateRip Meta results. + +CUERipper: + +- Shows AccurateRip Meta entries during fallback/reload lookup. +- Hides or disables the FreeDB submit button because AccurateRip Meta has no submission API. +- Hides or disables FreeDB-specific encoding and various-artist repair actions unless they still apply only to dormant FreeDB entries. +- Removes normal UI access to FreeDB advanced settings if those settings are not useful for AccurateRip Meta. + +## Error Handling And Networking + +AccurateRip Meta lookup should fail soft. Network errors, empty responses, invalid JSON, missing required album fields, or incompatible track counts should trace/log the issue and add no AccurateRip Meta entry. Existing local/CUE/tag/CTDB results must remain available. + +The request body should be built with JSON serialization rather than string concatenation. The implementation must reuse CUETools proxy settings where the existing metadata flow provides a proxy. No API key or secret should be introduced. + +If `_arturl` is empty or invalid, the metadata entry can still be shown without cover art. Image bytes should not be downloaded during the metadata mapping step; existing album-art consumers should fetch URLs as they do for CTDB cover art. + +## FreeDB Dormancy + +FreeDB is no longer an active provider after this change. The old FreeDB lookup, submit form, submit handler, advanced settings, and encoding/various-artist helper paths should be marked as dormant and deletion/adaptation candidates. + +Dormant code should not remain reachable from normal UI. If retaining dormant code prevents removal of the `Freedb` project reference, isolate it so the active solution can build cleanly and the dependency boundary is obvious. + +## Testing And Verification + +Add focused tests for the AccurateRip Meta mapper: + +- Valid JSON maps album, track, UPC, label, catalog number, ISRC, and cover URL correctly. +- `CatalogNum` maps to `LabelNo`. +- Empty optional fields do not throw. +- `Styles`, `Composers`, and `Conductors` do not leak into comments or tags. +- Track-count mismatches fail soft. + +Add lookup behavior tests where practical: + +- `CTDBMetadataSearch.Extensive` adds an AccurateRip Meta entry even when CTDB returns metadata. +- Non-extensive lookup does not query AccurateRip Meta. +- Provider failure does not remove existing results. + +Verification should include affected project builds and the broader solution build/test gate. If the current .NET 10 solution still needs temporary third-party patch application to build, apply and revert those patches only for verification. + +Manual smoke checks: + +- CUETools metadata chooser shows `AccurateRip Meta` with cover preview when `_arturl` exists. +- CUERipper reload/all-metadata lookup can show AccurateRip Meta. +- FreeDB submit/settings are not reachable in normal UI. diff --git a/global.json b/global.json new file mode 100644 index 000000000..17d728e19 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "10.0.300", + "rollForward": "latestFeature", + "allowPrerelease": false + } +}