WIP: 单元测试
Reviewing AudioMetadataReader.cs
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
namespace OCES.Resonance.Core.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AudioFileScanner 单元测试
|
||||||
|
/// </summary>
|
||||||
|
public class AudioFileScannerTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("test.wav", true)]
|
||||||
|
[InlineData("test.WAV", true)]
|
||||||
|
[InlineData("test.mp3", true)]
|
||||||
|
[InlineData("test.txt", false)]
|
||||||
|
[InlineData("", false)]
|
||||||
|
[InlineData("test.xlsx", false)]
|
||||||
|
public void IsSupportedAudioFile_ShouldWork(string input, bool expected)
|
||||||
|
{
|
||||||
|
AudioFileScanner scanner = new();
|
||||||
|
|
||||||
|
bool result = scanner.IsSupportedAudioFile(input);
|
||||||
|
|
||||||
|
Assert.Equal(expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ScanDirectory_ShouldWork()
|
||||||
|
{
|
||||||
|
string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 构造真实文件
|
||||||
|
File.WriteAllText(Path.Combine(tempDir, "a.wav"), "");
|
||||||
|
File.WriteAllText(Path.Combine(tempDir, "b.MP3"), "");
|
||||||
|
File.WriteAllText(Path.Combine(tempDir, "c.txt"), "");
|
||||||
|
File.WriteAllText(Path.Combine(tempDir, "d.xlsx"), "");
|
||||||
|
File.WriteAllText(Path.Combine(tempDir, "e.WAV"), "");
|
||||||
|
|
||||||
|
AudioFileScanner scanner = new();
|
||||||
|
|
||||||
|
List<string> result = scanner.ScanDirectory(tempDir).ToList();
|
||||||
|
|
||||||
|
Assert.Equal(3, result.Count);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(tempDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ScanDirectory_ShouldThrow_WhenDirectoryDoesNotExist()
|
||||||
|
{
|
||||||
|
AudioFileScanner scanner = new();
|
||||||
|
string nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
|
|
||||||
|
Assert.Throws<DirectoryNotFoundException>(() => scanner.ScanDirectory(nonExistentPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
|
||||||
|
namespace OCES.Resonance.Core.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AudioLibraryService 集成测试
|
||||||
|
/// </summary>
|
||||||
|
public class AudioLibraryServiceTests : IDisposable
|
||||||
|
{
|
||||||
|
readonly AudioLibraryService _service;
|
||||||
|
readonly string _testDirectory;
|
||||||
|
|
||||||
|
public AudioLibraryServiceTests()
|
||||||
|
{
|
||||||
|
_service = new AudioLibraryService();
|
||||||
|
_testDirectory = Path.Combine(Path.GetTempPath(), $"LibraryServiceTest_{Guid.NewGuid()}");
|
||||||
|
Directory.CreateDirectory(_testDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Directory.Exists(_testDirectory))
|
||||||
|
{
|
||||||
|
Directory.Delete(_testDirectory, true);
|
||||||
|
}
|
||||||
|
// 清理测试数据库
|
||||||
|
var dbPath = "default.db";
|
||||||
|
if (File.Exists(dbPath))
|
||||||
|
{
|
||||||
|
File.Delete(dbPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ScanAndImportToLibrary_空目录_返回零总数()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = await _service.ScanAndImportToLibrary(_testDirectory);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.TotalFiles.Should().Be(0);
|
||||||
|
result.SuccessCount.Should().Be(0);
|
||||||
|
result.ErrorCount.Should().Be(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ScanAndImportToLibrary_包含音频文件_成功导入()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wavFile = Path.Combine(_testDirectory, "test.wav");
|
||||||
|
File.WriteAllText(wavFile, "fake wav content");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.ScanAndImportToLibrary(_testDirectory);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.TotalFiles.Should().Be(1);
|
||||||
|
result.SuccessCount.Should().Be(1);
|
||||||
|
result.ErrorCount.Should().Be(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ScanAndImportToLibrary_多个文件_正确统计()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
File.WriteAllText(Path.Combine(_testDirectory, "audio1.wav"), "fake");
|
||||||
|
File.WriteAllText(Path.Combine(_testDirectory, "audio2.mp3"), "fake");
|
||||||
|
File.WriteAllText(Path.Combine(_testDirectory, "audio3.flac"), "fake");
|
||||||
|
File.WriteAllText(Path.Combine(_testDirectory, "document.txt"), "not audio");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.ScanAndImportToLibrary(_testDirectory);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.TotalFiles.Should().Be(3);
|
||||||
|
result.SuccessCount.Should().Be(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ScanAndImportToLibrary_递归扫描子目录()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var subDir = Path.Combine(_testDirectory, "subdir");
|
||||||
|
Directory.CreateDirectory(subDir);
|
||||||
|
File.WriteAllText(Path.Combine(_testDirectory, "root.wav"), "fake");
|
||||||
|
File.WriteAllText(Path.Combine(subDir, "sub.wav"), "fake");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.ScanAndImportToLibrary(_testDirectory);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.TotalFiles.Should().Be(2);
|
||||||
|
result.SuccessCount.Should().Be(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ScanResult_GetSummary_返回正确格式()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result = new ScanResult
|
||||||
|
{
|
||||||
|
TotalFiles = 10,
|
||||||
|
SuccessCount = 8,
|
||||||
|
SkipCount = 1,
|
||||||
|
ErrorCount = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var summary = result.GetSummary();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
summary.Should().Contain("10");
|
||||||
|
summary.Should().Contain("8");
|
||||||
|
summary.Should().Contain("1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ScanAndImportToLibrary_包含不支持格式_只导入音频()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
File.WriteAllText(Path.Combine(_testDirectory, "audio.wav"), "fake");
|
||||||
|
File.WriteAllText(Path.Combine(_testDirectory, "image.jpg"), "fake");
|
||||||
|
File.WriteAllText(Path.Combine(_testDirectory, "doc.pdf"), "fake");
|
||||||
|
File.WriteAllText(Path.Combine(_testDirectory, "text.txt"), "fake");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.ScanAndImportToLibrary(_testDirectory);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.TotalFiles.Should().Be(1);
|
||||||
|
result.SuccessCount.Should().Be(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
|
||||||
|
namespace OCES.Resonance.Core.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AudioMetadataReader 单元测试
|
||||||
|
/// </summary>
|
||||||
|
public class AudioMetadataReaderTests
|
||||||
|
{
|
||||||
|
readonly AudioMetadataReader _reader;
|
||||||
|
readonly string _testDirectory;
|
||||||
|
|
||||||
|
public AudioMetadataReaderTests()
|
||||||
|
{
|
||||||
|
this._reader = new AudioMetadataReader();
|
||||||
|
this._testDirectory = Path.Combine(Path.GetTempPath(), $"MetadataReaderTest_{Guid.NewGuid()}");
|
||||||
|
Directory.CreateDirectory(this._testDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReadMetadata_文件不存在_抛出FileNotFoundException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string nonExistentFile = Path.Combine(this._testDirectory, "non_existent.wav");
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Func<AudioFileMeta> act = () => this._reader.ReadMetadata(nonExistentFile);
|
||||||
|
act.Should().Throw<FileNotFoundException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReadMetadata_空文件_仍然返回元数据()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string emptyFile = Path.Combine(this._testDirectory, "empty.wav");
|
||||||
|
File.WriteAllText(emptyFile, "");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
AudioFileMeta result = this._reader.ReadMetadata(emptyFile);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Filename.Should().Be("empty.wav");
|
||||||
|
result.Path.Should().Be(Path.GetFullPath(emptyFile));
|
||||||
|
result.Md5.Should().NotBeNullOrEmpty();
|
||||||
|
result.UniqueId.Should().NotBeNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReadMetadata_生成唯一ID_每次调用都不同()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string testFile = Path.Combine(this._testDirectory, "test.wav");
|
||||||
|
File.WriteAllText(testFile, "fake audio content");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
AudioFileMeta result1 = this._reader.ReadMetadata(testFile);
|
||||||
|
AudioFileMeta result2 = this._reader.ReadMetadata(testFile);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result1.UniqueId.Should().NotBe(result2.UniqueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReadMetadata_相同文件_MD5相同()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var testFile = Path.Combine(this._testDirectory, "test.wav");
|
||||||
|
File.WriteAllText(testFile, "fake audio content");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result1 = this._reader.ReadMetadata(testFile);
|
||||||
|
var result2 = this._reader.ReadMetadata(testFile);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result1.Md5.Should().Be(result2.Md5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(".wav", "WAV")]
|
||||||
|
[InlineData(".mp3", "MP3")]
|
||||||
|
[InlineData(".flac", "FLAC")]
|
||||||
|
[InlineData(".aiff", "AIFF")]
|
||||||
|
public void ReadMetadata_不同格式_正确设置Type字段(string extension, string expectedType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string testFile = Path.Combine(this._testDirectory, $"test{extension}");
|
||||||
|
File.WriteAllText(testFile, "fake audio content");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
AudioFileMeta result = this._reader.ReadMetadata(testFile);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Type.Should().Be(expectedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReadMetadata_提取文件夹信息()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string subDir = Path.Combine(this._testDirectory, "SubFolder");
|
||||||
|
Directory.CreateDirectory(subDir);
|
||||||
|
string testFile = Path.Combine(subDir, "test.wav");
|
||||||
|
File.WriteAllText(testFile, "fake audio content");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
AudioFileMeta result = this._reader.ReadMetadata(testFile);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Folder.Should().Be("SubFolder");
|
||||||
|
result.Directory.Should().Be(subDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReadMetadata_设置DateAdded为当前时间()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string testFile = Path.Combine(this._testDirectory, "test.wav");
|
||||||
|
File.WriteAllText(testFile, "fake audio content");
|
||||||
|
DateTime beforeTime = DateTime.Now;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
AudioFileMeta result = this._reader.ReadMetadata(testFile);
|
||||||
|
DateTime afterTime = DateTime.Now;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.DateAdded.Should().BeOnOrAfter(beforeTime);
|
||||||
|
result.DateAdded.Should().BeOnOrBefore(afterTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||||
|
<PackageReference Include="FluentAssertions" Version="8.9.0" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Core\Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
using System.Data.SQLite;
|
||||||
|
using FluentAssertions;
|
||||||
|
|
||||||
|
namespace OCES.Resonance.Core.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Database 单元测试
|
||||||
|
/// </summary>
|
||||||
|
public class DatabaseTests : IDisposable
|
||||||
|
{
|
||||||
|
readonly string _testDbName;
|
||||||
|
readonly string _testDbPath;
|
||||||
|
|
||||||
|
public DatabaseTests()
|
||||||
|
{
|
||||||
|
_testDbName = $"test_{Guid.NewGuid():N}";
|
||||||
|
_testDbPath = $"{_testDbName}.db";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (File.Exists(_testDbPath))
|
||||||
|
{
|
||||||
|
File.Delete(_testDbPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetConnection_返回有效连接()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
using var connection = Database.GetConnection(_testDbName);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
connection.Should().NotBeNull();
|
||||||
|
connection.Should().BeOfType<SQLiteConnection>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InitializeDatabase_创建表结构()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
Database.InitializeDatabase();
|
||||||
|
|
||||||
|
// Assert - 验证表已创建(通过尝试查询)
|
||||||
|
using var connection = Database.GetConnection();
|
||||||
|
connection.Open();
|
||||||
|
var command = connection.CreateCommand();
|
||||||
|
command.CommandText = "SELECT COUNT(*) FROM audio_files;";
|
||||||
|
var count = command.ExecuteScalar();
|
||||||
|
count.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddEntry_添加有效记录_返回True()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Database.InitializeDatabase();
|
||||||
|
var meta = CreateTestMeta();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = Database.AddEntry(meta);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddEntry_添加记录后_可以查询到()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Database.InitializeDatabase();
|
||||||
|
var meta = CreateTestMeta();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Database.AddEntry(meta);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
using var connection = Database.GetConnection();
|
||||||
|
connection.Open();
|
||||||
|
var command = connection.CreateCommand();
|
||||||
|
command.CommandText = "SELECT filename FROM audio_files WHERE unique_id = @id;";
|
||||||
|
var param = command.CreateParameter();
|
||||||
|
param.ParameterName = "@id";
|
||||||
|
param.Value = meta.UniqueId;
|
||||||
|
command.Parameters.Add(param);
|
||||||
|
var filename = command.ExecuteScalar() as string;
|
||||||
|
filename.Should().Be(meta.Filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddEntries_批量添加_返回正确数量()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Database.InitializeDatabase();
|
||||||
|
var entries = new List<AudioFileMeta>
|
||||||
|
{
|
||||||
|
CreateTestMeta("file1.wav"),
|
||||||
|
CreateTestMeta("file2.wav"),
|
||||||
|
CreateTestMeta("file3.wav"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var count = Database.AddEntries(entries);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
count.Should().Be(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EntryExists_记录存在_返回True()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Database.InitializeDatabase();
|
||||||
|
var meta = CreateTestMeta();
|
||||||
|
Database.AddEntry(meta);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exists = Database.EntryExists(meta.Md5, meta.Path);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exists.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EntryExists_记录不存在_返回False()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Database.InitializeDatabase();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exists = Database.EntryExists("non_existent_md5", "/non/existent/path.wav");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exists.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddEntry_重复UniqueId_第二次添加失败()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Database.InitializeDatabase();
|
||||||
|
var meta1 = CreateTestMeta();
|
||||||
|
var meta2 = CreateTestMeta();
|
||||||
|
meta2.UniqueId = meta1.UniqueId; // 使用相同的 UniqueId
|
||||||
|
Database.AddEntry(meta1);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = Database.AddEntry(meta2);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
static AudioFileMeta CreateTestMeta(string filename = "test.wav")
|
||||||
|
{
|
||||||
|
return new AudioFileMeta
|
||||||
|
{
|
||||||
|
Id = 0, // 自增 ID,由数据库生成
|
||||||
|
UniqueId = Guid.NewGuid().ToString("N"),
|
||||||
|
Md5 = Guid.NewGuid().ToString("N"),
|
||||||
|
Path = $"/test/path/{filename}",
|
||||||
|
Filename = filename,
|
||||||
|
Folder = "test",
|
||||||
|
Directory = "/test/path",
|
||||||
|
Duration = 10.5,
|
||||||
|
TotalSamples = 441000,
|
||||||
|
BitDepth = 24,
|
||||||
|
Channels = 2,
|
||||||
|
SampleRate = 44100,
|
||||||
|
Type = "WAV",
|
||||||
|
DateAdded = DateTime.Now,
|
||||||
|
OriginalModificationDate = DateTime.Now.AddDays(-1),
|
||||||
|
OriginationTime = DateTime.Now.AddDays(-1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AudioFileMeta
|
public class AudioFileMeta
|
||||||
{
|
{
|
||||||
|
|
||||||
#region 必备条目
|
#region 必备条目
|
||||||
/// <summary>主键ID,音频文件的唯一数字标识符</summary>
|
/// <summary>主键ID,音频文件的唯一数字标识符</summary>
|
||||||
public required int Id { get; set; }
|
public required int Id { get; set; }
|
||||||
@@ -29,9 +30,6 @@ public class AudioFileMeta
|
|||||||
|
|
||||||
/// <summary>时长,音频文件的播放长度(通常以秒为单位)</summary>
|
/// <summary>时长,音频文件的播放长度(通常以秒为单位)</summary>
|
||||||
public required double Duration { get; set; }
|
public required double Duration { get; set; }
|
||||||
|
|
||||||
/// <summary>时长,以采样数记录的时长</summary>
|
|
||||||
public required uint TotalSamples { get; set; }
|
|
||||||
|
|
||||||
/// <summary>位深度,音频采样位深(如 16、24、32 bit)</summary>
|
/// <summary>位深度,音频采样位深(如 16、24、32 bit)</summary>
|
||||||
public required int BitDepth { get; set; }
|
public required int BitDepth { get; set; }
|
||||||
@@ -40,7 +38,7 @@ public class AudioFileMeta
|
|||||||
public required int Channels { get; set; }
|
public required int Channels { get; set; }
|
||||||
|
|
||||||
/// <summary>采样率,如 44100、48000、96000 Hz</summary>
|
/// <summary>采样率,如 44100、48000、96000 Hz</summary>
|
||||||
public required int SampleRate { get; set; }
|
public required double SampleRate { get; set; }
|
||||||
|
|
||||||
/// <summary>文件类型,音频格式(WAV、MP3、AIFF、FLAC等)</summary>
|
/// <summary>文件类型,音频格式(WAV、MP3、AIFF、FLAC等)</summary>
|
||||||
public required string Type { get; set; }
|
public required string Type { get; set; }
|
||||||
@@ -129,9 +127,13 @@ public class AudioFileMeta
|
|||||||
|
|
||||||
/// <summary>CD标题,原始CD专辑名称</summary>
|
/// <summary>CD标题,原始CD专辑名称</summary>
|
||||||
public string? CdTitle { get; set; }
|
public string? CdTitle { get; set; }
|
||||||
|
|
||||||
|
public int? DiscNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>曲目标题,音乐或音轨的标题</summary>
|
/// <summary>曲目标题,音乐或音轨的标题</summary>
|
||||||
public string? TrackTitle { get; set; }
|
public string? TrackTitle { get; set; }
|
||||||
|
|
||||||
|
public int? TrackNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>剧集,所属剧集或系列编号</summary>
|
/// <summary>剧集,所属剧集或系列编号</summary>
|
||||||
public string? Episode { get; set; }
|
public string? Episode { get; set; }
|
||||||
@@ -153,7 +155,7 @@ public class AudioFileMeta
|
|||||||
|
|
||||||
public DateTime? ReleaseDate { get; set; }
|
public DateTime? ReleaseDate { get; set; }
|
||||||
|
|
||||||
public string? TrackYear { get; set; }
|
public int? TrackYear { get; set; }
|
||||||
|
|
||||||
/// <summary>是否已编辑,布尔值(0=否,1=是)</summary>
|
/// <summary>是否已编辑,布尔值(0=否,1=是)</summary>
|
||||||
public bool? IsEdited { get; set; }
|
public bool? IsEdited { get; set; }
|
||||||
@@ -164,7 +166,7 @@ public class AudioFileMeta
|
|||||||
/// <summary>位置,录音地点或存储位置</summary>
|
/// <summary>位置,录音地点或存储位置</summary>
|
||||||
public string? Location { get; set; }
|
public string? Location { get; set; }
|
||||||
|
|
||||||
/// <summary>分组,用于组织管理的分组标识</summary>
|
/// <summary>Content group description Used if the sound belongs to a larger category of sounds/music. For example, classical music is often sorted in different musical sections (e.g. "Piano Concerto").</summary>
|
||||||
public string? Group { get; set; }
|
public string? Group { get; set; }
|
||||||
|
|
||||||
/// <summary>标记点,音频内的关键时间点标记</summary>
|
/// <summary>标记点,音频内的关键时间点标记</summary>
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ public class AudioFileMetaValidator
|
|||||||
/// <param name="meta">音频元数据对象</param>
|
/// <param name="meta">音频元数据对象</param>
|
||||||
/// <param name="errors">验证错误列表</param>
|
/// <param name="errors">验证错误列表</param>
|
||||||
/// <returns>是否验证通过</returns>
|
/// <returns>是否验证通过</returns>
|
||||||
public bool Validate(AudioFileMeta meta, out List<string> errors)
|
public bool Validate(AudioFileMeta? meta, out List<string> errors)
|
||||||
{
|
{
|
||||||
errors = new List<string>();
|
errors = [];
|
||||||
|
|
||||||
if (meta == null)
|
if (meta == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ namespace OCES.Resonance.Core;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AudioFileScanner
|
public class AudioFileScanner
|
||||||
{
|
{
|
||||||
private static readonly string[] SupportedExtensions =
|
static readonly string[] SupportedExtensions =
|
||||||
{
|
[
|
||||||
".wav", ".mp3", ".flac", ".aiff", ".aif", ".m4a", ".ogg", ".wma", ".bwf", ".wav64"
|
".wav", ".mp3", ".flac", ".aiff", ".aif", ".m4a", ".ogg", ".wma", ".bwf", ".wav64",
|
||||||
};
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 扫描指定目录,返回所有音频文件路径
|
/// 扫描指定目录,返回所有音频文件路径
|
||||||
@@ -23,12 +23,11 @@ public class AudioFileScanner
|
|||||||
throw new DirectoryNotFoundException($"目录不存在:{directoryPath}");
|
throw new DirectoryNotFoundException($"目录不存在:{directoryPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
SearchOption searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||||
|
|
||||||
return SupportedExtensions
|
return SupportedExtensions
|
||||||
.SelectMany(ext => Directory.EnumerateFiles(directoryPath, $"*{ext}", searchOption))
|
.SelectMany(ext => Directory.EnumerateFiles(directoryPath, $"*{ext}", searchOption))
|
||||||
.Where(IsSupportedAudioFile)
|
.Where(IsSupportedAudioFile);
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace OCES.Resonance.Core;
|
namespace OCES.Resonance.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -5,14 +7,8 @@ namespace OCES.Resonance.Core;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AudioLibraryService
|
public class AudioLibraryService
|
||||||
{
|
{
|
||||||
private readonly AudioFileScanner _scanner;
|
readonly AudioFileScanner _scanner = new();
|
||||||
private readonly AudioMetadataReader _metadataReader;
|
readonly AudioMetadataReader _metadataReader = new();
|
||||||
|
|
||||||
public AudioLibraryService()
|
|
||||||
{
|
|
||||||
_scanner = new AudioFileScanner();
|
|
||||||
_metadataReader = new AudioMetadataReader();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 扫描目录并将结果添加到数据库
|
/// 扫描目录并将结果添加到数据库
|
||||||
@@ -32,14 +28,14 @@ public class AudioLibraryService
|
|||||||
SuccessCount = 0,
|
SuccessCount = 0,
|
||||||
SkipCount = 0,
|
SkipCount = 0,
|
||||||
ErrorCount = 0,
|
ErrorCount = 0,
|
||||||
Errors = new List<string>()
|
Errors = [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化数据库
|
// 初始化数据库
|
||||||
Database.InitializeDatabase();
|
Database.InitializeDatabase();
|
||||||
|
|
||||||
// 扫描所有音频文件
|
// 扫描所有音频文件
|
||||||
var audioFiles = _scanner.ScanDirectory(directoryPath).ToList();
|
List<string> audioFiles = this._scanner.ScanDirectory(directoryPath).ToList();
|
||||||
result.TotalFiles = audioFiles.Count;
|
result.TotalFiles = audioFiles.Count;
|
||||||
|
|
||||||
if (audioFiles.Count == 0)
|
if (audioFiles.Count == 0)
|
||||||
@@ -47,25 +43,18 @@ public class AudioLibraryService
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
var metadataList = new List<AudioFileMeta>();
|
List<AudioFileMeta> metadataList = [];
|
||||||
|
|
||||||
// 读取每个文件的元数据
|
// 读取每个文件的元数据
|
||||||
foreach (var filePath in audioFiles)
|
foreach (string filePath in audioFiles)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 计算 MD5 用于查重
|
// 计算 MD5 用于查重
|
||||||
var md5 = CalculateMd5ForFile(filePath);
|
string md5 = CalculateMd5ForFile(filePath);
|
||||||
|
|
||||||
// 检查是否已存在
|
|
||||||
if (skipExisting && Database.EntryExists(md5, filePath))
|
|
||||||
{
|
|
||||||
result.SkipCount++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取元数据
|
// 读取元数据
|
||||||
var metadata = _metadataReader.ReadMetadata(filePath);
|
AudioFileMeta metadata = this._metadataReader.ReadMetadata(filePath);
|
||||||
metadataList.Add(metadata);
|
metadataList.Add(metadata);
|
||||||
result.SuccessCount++;
|
result.SuccessCount++;
|
||||||
}
|
}
|
||||||
@@ -77,7 +66,8 @@ public class AudioLibraryService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 批量入库
|
// 批量入库
|
||||||
if (metadataList.Count > 0)
|
if (metadataList.Count <= 0)
|
||||||
|
return await Task.FromResult(result);
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -98,10 +88,10 @@ public class AudioLibraryService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static string CalculateMd5ForFile(string filePath)
|
private static string CalculateMd5ForFile(string filePath)
|
||||||
{
|
{
|
||||||
using var md5 = System.Security.Cryptography.MD5.Create();
|
using MD5 md5 = MD5.Create();
|
||||||
using var stream = File.OpenRead(filePath);
|
using FileStream stream = File.OpenRead(filePath);
|
||||||
var hash = md5.ComputeHash(stream);
|
byte[] hash = md5.ComputeHash(stream);
|
||||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
return Convert.ToHexString(hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +113,7 @@ public class ScanResult
|
|||||||
public int ErrorCount { get; set; }
|
public int ErrorCount { get; set; }
|
||||||
|
|
||||||
/// <summary>错误详情列表</summary>
|
/// <summary>错误详情列表</summary>
|
||||||
public List<string> Errors { get; set; } = new();
|
public List<string> Errors { get; set; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取人类可读的统计报告
|
/// 获取人类可读的统计报告
|
||||||
|
|||||||
@@ -11,62 +11,36 @@ public class AudioMetadataReader
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从音频文件读取元数据
|
/// 从音频文件读取元数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePath">音频文件路径</param>
|
/// <param name="audioFile">音频文件路径</param>
|
||||||
/// <returns>音频元数据对象</returns>
|
/// <returns>音频元数据对象</returns>
|
||||||
public AudioFileMeta ReadMetadata(string filePath)
|
public AudioFileMeta ReadMetadata(FileInfo audioFile)
|
||||||
{
|
{
|
||||||
if (!File.Exists(filePath))
|
if (!audioFile.Exists)
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException($"文件不存在:{filePath}");
|
throw new FileNotFoundException($"文件不存在:{audioFile}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var track = new Track(filePath);
|
Track track = new(audioFile.FullName);
|
||||||
var fileInfo = new FileInfo(filePath);
|
|
||||||
|
|
||||||
// 获取音频技术参数
|
|
||||||
var durationSeconds = track.DurationMs / 1000.0;
|
|
||||||
var sampleRate = track.SampleRate;
|
|
||||||
var channels = track.ChannelsArrangement?.NbChannels ?? 2;
|
|
||||||
var bitDepth = GetBitDepth(track);
|
|
||||||
var totalSamples = (uint)(durationSeconds * sampleRate);
|
|
||||||
|
|
||||||
// 提取路径信息
|
|
||||||
var fileName = Path.GetFileName(filePath);
|
|
||||||
var folder = Path.GetDirectoryName(filePath) ?? string.Empty;
|
|
||||||
var directory = Path.GetDirectoryName(filePath) ?? string.Empty;
|
|
||||||
var folderName = new DirectoryInfo(folder).Name;
|
|
||||||
|
|
||||||
// 生成唯一标识
|
|
||||||
var md5 = CalculateMd5(filePath);
|
|
||||||
var uniqueId = GenerateUuid();
|
|
||||||
|
|
||||||
// 获取文件修改时间
|
|
||||||
var lastWriteTime = fileInfo.LastWriteTime;
|
|
||||||
var createTime = fileInfo.CreationTime;
|
|
||||||
|
|
||||||
// 获取文件扩展名作为类型
|
|
||||||
var fileType = Path.GetExtension(filePath).TrimStart('.').ToUpperInvariant();
|
|
||||||
|
|
||||||
// 映射 ATL 元数据到 AudioFileMeta
|
// 映射 ATL 元数据到 AudioFileMeta
|
||||||
var meta = new AudioFileMeta
|
AudioFileMeta meta = new()
|
||||||
{
|
{
|
||||||
// 必备条目
|
// 必备条目
|
||||||
Id = 0, // 自增 ID,由数据库生成
|
Id = 0, // 自增 ID,由数据库生成
|
||||||
UniqueId = uniqueId,
|
UniqueId = GenerateUuid(),
|
||||||
Md5 = md5,
|
Md5 = CalculateMd5(audioFile.FullName),
|
||||||
Path = Path.GetFullPath(filePath),
|
Path = audioFile.FullName,
|
||||||
Filename = fileName,
|
Filename = audioFile.Name,
|
||||||
Folder = folderName,
|
Folder = audioFile.Directory?.Name ?? string.Empty,
|
||||||
Directory = directory,
|
Directory = audioFile.DirectoryName ?? string.Empty,
|
||||||
Duration = durationSeconds,
|
Duration = track.DurationMs / 1000,
|
||||||
TotalSamples = totalSamples,
|
BitDepth = track.BitDepth,
|
||||||
BitDepth = bitDepth,
|
Channels = track.ChannelsArrangement.NbChannels,
|
||||||
Channels = channels,
|
SampleRate = track.SampleRate,
|
||||||
SampleRate = (int)sampleRate,
|
Type = audioFile.Extension.TrimStart('.').ToUpperInvariant(),
|
||||||
Type = fileType,
|
|
||||||
DateAdded = DateTime.Now,
|
DateAdded = DateTime.Now,
|
||||||
OriginalModificationDate = lastWriteTime,
|
OriginalModificationDate = audioFile.LastWriteTime,
|
||||||
OriginationTime = createTime,
|
OriginationTime = audioFile.CreationTime,
|
||||||
|
|
||||||
// 可选条目
|
// 可选条目
|
||||||
Bpm = track.BPM > 0 ? track.BPM : null,
|
Bpm = track.BPM > 0 ? track.BPM : null,
|
||||||
@@ -74,8 +48,17 @@ public class AudioMetadataReader
|
|||||||
Genre = track.Genre,
|
Genre = track.Genre,
|
||||||
Artist = track.Artist,
|
Artist = track.Artist,
|
||||||
Composer = track.Composer,
|
Composer = track.Composer,
|
||||||
|
Comments = track.Comment,
|
||||||
Publisher = track.Publisher,
|
Publisher = track.Publisher,
|
||||||
Copyright = track.Copyright,
|
Copyright = track.Copyright,
|
||||||
|
CdTitle = track.Album,
|
||||||
|
DiscNumber = track.DiscNumber,
|
||||||
|
TrackTitle = track.Title,
|
||||||
|
ReleaseDate = track.OriginalReleaseDate,
|
||||||
|
TrackYear = track.Year,
|
||||||
|
TrackNumber = track.TrackNumber,
|
||||||
|
Group = track.Group,
|
||||||
|
|
||||||
|
|
||||||
// BWF 特定字段 (通过附加数据获取)
|
// BWF 特定字段 (通过附加数据获取)
|
||||||
CodingHistory = GetBwfField(track, "CodingHistory"),
|
CodingHistory = GetBwfField(track, "CodingHistory"),
|
||||||
@@ -105,69 +88,26 @@ public class AudioMetadataReader
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取自定义字段值
|
|
||||||
/// </summary>
|
|
||||||
private static string? GetCustomField(Track track, int index)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string fieldName = $"User{index}";
|
|
||||||
if (track.AdditionalFields != null && track.AdditionalFields.TryGetValue(fieldName, out string? field))
|
|
||||||
{
|
|
||||||
return field;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// 忽略自定义字段读取错误
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取音频位深度
|
|
||||||
/// </summary>
|
|
||||||
private static int GetBitDepth(Track track)
|
|
||||||
{
|
|
||||||
// ATL 可能通过不同方式提供位深信息
|
|
||||||
if (track.BitDepth > 0)
|
|
||||||
{
|
|
||||||
return track.BitDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据格式推断默认位深
|
|
||||||
var extension = Path.GetExtension(track.Path).ToLowerInvariant();
|
|
||||||
return extension switch
|
|
||||||
{
|
|
||||||
".wav" or ".bwf" or ".wav64" => 24, // 现代 WAV 通常 24bit
|
|
||||||
".flac" => 24,
|
|
||||||
".aiff" or ".aif" => 16,
|
|
||||||
".mp3" => 16, // MP3 实际没有位深概念,默认 16
|
|
||||||
_ => 16
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 计算文件 MD5 值
|
/// 计算文件 MD5 值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePath">文件路径</param>
|
/// <param name="filePath">文件路径</param>
|
||||||
/// <returns>MD5 哈希值(十六进制字符串)</returns>
|
/// <returns>MD5 哈希值(十六进制字符串)</returns>
|
||||||
private static string CalculateMd5(string filePath)
|
static string CalculateMd5(string filePath)
|
||||||
{
|
{
|
||||||
using var md5 = MD5.Create();
|
using MD5 md5 = MD5.Create();
|
||||||
using var stream = File.OpenRead(filePath);
|
using FileStream stream = File.OpenRead(filePath);
|
||||||
|
|
||||||
var hash = md5.ComputeHash(stream);
|
byte[] hash = md5.ComputeHash(stream);
|
||||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
return Convert.ToHexStringLower(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 生成 UUID
|
/// 生成 UUID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>UUID 字符串</returns>
|
/// <returns>UUID 字符串</returns>
|
||||||
private static string GenerateUuid()
|
static string GenerateUuid()
|
||||||
{
|
{
|
||||||
return Guid.NewGuid().ToString("N"); // 无连字符的 UUID
|
return Guid.NewGuid().ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public static class Database
|
|||||||
/// <returns>数据库连接</returns>
|
/// <returns>数据库连接</returns>
|
||||||
public static IDbConnection GetConnection(string dbName = "default")
|
public static IDbConnection GetConnection(string dbName = "default")
|
||||||
{
|
{
|
||||||
var connectionString = $"Data Source={dbName}.db;Version=3;";
|
string connectionString = $"Data Source={dbName}.db;Version=3;";
|
||||||
return new SQLiteConnection(connectionString);
|
return new SQLiteConnection(connectionString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ public static class Database
|
|||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
CREATE TABLE IF NOT EXISTS audio_files (
|
CREATE TABLE IF NOT EXISTS sounds (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
unique_id TEXT NOT NULL UNIQUE,
|
unique_id TEXT NOT NULL UNIQUE,
|
||||||
short_id TEXT,
|
short_id TEXT,
|
||||||
@@ -83,7 +83,7 @@ public static class Database
|
|||||||
is_edited INTEGER,
|
is_edited INTEGER,
|
||||||
is_split INTEGER,
|
is_split INTEGER,
|
||||||
location TEXT,
|
location TEXT,
|
||||||
group TEXT,
|
[group] TEXT,
|
||||||
markers TEXT,
|
markers TEXT,
|
||||||
comments TEXT,
|
comments TEXT,
|
||||||
notes TEXT,
|
notes TEXT,
|
||||||
@@ -99,7 +99,7 @@ public static class Database
|
|||||||
user6 TEXT,
|
user6 TEXT,
|
||||||
user7 TEXT,
|
user7 TEXT,
|
||||||
user8 TEXT,
|
user8 TEXT,
|
||||||
|
|
||||||
INDEX idx_md5 (md5),
|
INDEX idx_md5 (md5),
|
||||||
INDEX idx_path (path),
|
INDEX idx_path (path),
|
||||||
INDEX idx_unique_id (unique_id)
|
INDEX idx_unique_id (unique_id)
|
||||||
@@ -131,7 +131,7 @@ public static class Database
|
|||||||
artist, composer, designer, recordist, publisher, manufacturer,
|
artist, composer, designer, recordist, publisher, manufacturer,
|
||||||
originator, originator_ref, project_name, library, cd_title,
|
originator, originator_ref, project_name, library, cd_title,
|
||||||
track_title, episode, scene, take, tape, cue_number, sync_point,
|
track_title, episode, scene, take, tape, cue_number, sync_point,
|
||||||
release_date, track_year, is_edited, is_split, location, group,
|
release_date, track_year, is_edited, is_split, location, [group],
|
||||||
markers, comments, notes, copyright, coding_history, microphone,
|
markers, comments, notes, copyright, coding_history, microphone,
|
||||||
mic_perspective, user1, user2, user3, user4, user5, user6, user7, user8
|
mic_perspective, user1, user2, user3, user4, user5, user6, user7, user8
|
||||||
) VALUES (
|
) VALUES (
|
||||||
@@ -182,7 +182,7 @@ public static class Database
|
|||||||
artist, composer, designer, recordist, publisher, manufacturer,
|
artist, composer, designer, recordist, publisher, manufacturer,
|
||||||
originator, originator_ref, project_name, library, cd_title,
|
originator, originator_ref, project_name, library, cd_title,
|
||||||
track_title, episode, scene, take, tape, cue_number, sync_point,
|
track_title, episode, scene, take, tape, cue_number, sync_point,
|
||||||
release_date, track_year, is_edited, is_split, location, group,
|
release_date, track_year, is_edited, is_split, location, [group],
|
||||||
markers, comments, notes, copyright, coding_history, microphone,
|
markers, comments, notes, copyright, coding_history, microphone,
|
||||||
mic_perspective, user1, user2, user3, user4, user5, user6, user7, user8
|
mic_perspective, user1, user2, user3, user4, user5, user6, user7, user8
|
||||||
) VALUES (
|
) VALUES (
|
||||||
|
|||||||
@@ -2,15 +2,44 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{855F941D-5DA0-479A-BB79-5C7CE72CD34B}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{855F941D-5DA0-479A-BB79-5C7CE72CD34B}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Tests", "Core.Tests\Core.Tests.csproj", "{24F92458-FB39-44BE-A32F-41275183AF1B}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Release|Any CPU.Build.0 = Release|Any CPU
|
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{855F941D-5DA0-479A-BB79-5C7CE72CD34B}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{24F92458-FB39-44BE-A32F-41275183AF1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{24F92458-FB39-44BE-A32F-41275183AF1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{24F92458-FB39-44BE-A32F-41275183AF1B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{24F92458-FB39-44BE-A32F-41275183AF1B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{24F92458-FB39-44BE-A32F-41275183AF1B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{24F92458-FB39-44BE-A32F-41275183AF1B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{24F92458-FB39-44BE-A32F-41275183AF1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{24F92458-FB39-44BE-A32F-41275183AF1B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{24F92458-FB39-44BE-A32F-41275183AF1B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{24F92458-FB39-44BE-A32F-41275183AF1B}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{24F92458-FB39-44BE-A32F-41275183AF1B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{24F92458-FB39-44BE-A32F-41275183AF1B}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFormat_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fe2d95a1d7b987bbb7785b2ad7e2e3e29075ba79cdf7619dd26c10d266c5_003FFormat_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue"><AssemblyExplorer>
|
||||||
|
<Assembly Path="/Users/oliver/.nuget/packages/fluentassertions/8.9.0/lib/net6.0/FluentAssertions.dll" />
|
||||||
|
</AssemblyExplorer></s:String>
|
||||||
|
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=5d87cd84_002Da775_002D4e8b_002D859b_002D8460b06eac04/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="IsSupportedAudioFile_ShouldWork" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||||
|
<TestAncestor>
|
||||||
|
<TestId>xUnit::24F92458-FB39-44BE-A32F-41275183AF1B::net10.0::OCES.Resonance.Core.Tests.AudioFileScannerTests</TestId>
|
||||||
|
<TestId>xUnit::24F92458-FB39-44BE-A32F-41275183AF1B::net10.0::OCES.Resonance.Core.Tests.AudioMetadataReaderTests.ReadMetadata_文件不存在_抛出FileNotFoundException</TestId>
|
||||||
|
</TestAncestor>
|
||||||
|
</SessionState></s:String></wpf:ResourceDictionary>
|
||||||
Reference in New Issue
Block a user