feat: AudioMetadataReader
This commit is contained in:
Regular → Executable
+12
-7
@@ -47,13 +47,16 @@ public class AudioFileMeta
|
||||
public required DateTime DateAdded { get; set; }
|
||||
|
||||
/// <summary>修改日期,音频创建或录制的日期</summary>
|
||||
public required DateTime OriginalModificationDate { get; set; }
|
||||
public required DateTime LastWriteTime { get; set; }
|
||||
|
||||
/// <summary>创建时间,音频文件的原始创建时间</summary>
|
||||
public required DateTime OriginationTime { get; set; }
|
||||
public required DateTime CreationTime { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>音效名称,不含文件前缀的简短效果名(如 "Air Pressure Release Wet Short 01")</summary>
|
||||
public string? FxName { get; set; }
|
||||
|
||||
///<summary>声道配置</summary>
|
||||
public string? ChannelLayout { get; set; }
|
||||
|
||||
@@ -122,9 +125,10 @@ public class AudioFileMeta
|
||||
/// <summary>来源引用,原始来源的参考编号</summary>
|
||||
public string? BwfOriginatorRef { get; set; }
|
||||
|
||||
public DateTime? BwfDate { get; set; }
|
||||
/// <summary>编码历史,BWF格式的编码历史记录</summary>
|
||||
public string? BwfCodingHistory { get; set; }
|
||||
|
||||
public string? BwfDescription { get; set; }
|
||||
public byte[]? BwfUmid { get; set; }
|
||||
|
||||
/// <summary>项目名称,所属制作项目</summary>
|
||||
public string? ProjectName { get; set; }
|
||||
@@ -188,13 +192,14 @@ public class AudioFileMeta
|
||||
/// <summary>版权,版权声明信息</summary>
|
||||
public string? Copyright { get; set; }
|
||||
|
||||
/// <summary>编码历史,BWF格式的编码历史记录</summary>
|
||||
public string? CodingHistory { get; set; }
|
||||
|
||||
/// <summary>麦克风,录音使用的麦克风型号或设置</summary>
|
||||
public string? Microphone { get; set; }
|
||||
|
||||
public string? MicPerspective { get; set; }
|
||||
|
||||
public byte[]? Artwork { get; set; }
|
||||
|
||||
public byte[]? Waveform { get; set; }
|
||||
|
||||
// 用户自定义字段(共8个)
|
||||
|
||||
|
||||
Regular → Executable
+2
-2
@@ -38,8 +38,8 @@ public class AudioFileMetaValidator
|
||||
|
||||
// 验证日期字段
|
||||
ValidateRequiredField(meta.DateAdded, "DateAdded", errors);
|
||||
ValidateRequiredField(meta.OriginalModificationDate, "OriginalModificationDate", errors);
|
||||
ValidateRequiredField(meta.OriginationTime, "OriginationTime", errors);
|
||||
ValidateRequiredField(meta.LastWriteTime, "LastWriteTime", errors);
|
||||
ValidateRequiredField(meta.CreationTime, "CreationTime", errors);
|
||||
|
||||
// 验证文件路径是否存在
|
||||
if (!File.Exists(meta.Path))
|
||||
|
||||
Regular → Executable
Regular → Executable
+64
-36
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using ATL;
|
||||
|
||||
@@ -27,7 +28,7 @@ public class AudioMetadataReader
|
||||
{
|
||||
// 必备条目
|
||||
Id = 0, // 自增 ID,由数据库生成
|
||||
UniqueId = GenerateUuid(),
|
||||
UniqueId = Guid.NewGuid().ToString(),
|
||||
Md5 = CalculateMd5(audioFile.FullName),
|
||||
Path = audioFile.FullName,
|
||||
Filename = audioFile.Name,
|
||||
@@ -39,52 +40,88 @@ public class AudioMetadataReader
|
||||
SampleRate = track.SampleRate,
|
||||
Type = audioFile.Extension.TrimStart('.').ToUpperInvariant(),
|
||||
DateAdded = DateTime.Now,
|
||||
OriginalModificationDate = audioFile.LastWriteTime,
|
||||
OriginationTime = audioFile.CreationTime,
|
||||
LastWriteTime = audioFile.LastWriteTime,
|
||||
CreationTime = audioFile.CreationTime,
|
||||
|
||||
// 可选条目
|
||||
Bpm = track.BPM > 0 ? track.BPM : null,
|
||||
Description = track.Description,
|
||||
Genre = track.Genre,
|
||||
Artist = track.Artist,
|
||||
Composer = track.Composer,
|
||||
Comments = track.Comment,
|
||||
Publisher = track.Publisher,
|
||||
Copyright = track.Copyright,
|
||||
CdTitle = track.Album,
|
||||
DiscNumber = track.DiscNumber,
|
||||
TrackTitle = track.Title,
|
||||
ReleaseDate = track.OriginalReleaseDate,
|
||||
TrackYear = track.Year,
|
||||
TrackNumber = track.TrackNumber,
|
||||
Group = track.Group,
|
||||
Artist = track.Artist,
|
||||
Artwork = track.EmbeddedPictures?.FirstOrDefault()?.PictureData,
|
||||
Bpm = track.BPM > 0 ? track.BPM : null,
|
||||
Category = GetField(track, "ixml.ASWG.category", "ixml.USER.CATEGORY"),
|
||||
CatId = GetField(track, "ixml.ASWG.catId", "ixml.USER.CATID"),
|
||||
CdTitle = track.Album,
|
||||
ChannelLayout = track.ChannelsArrangement?.ToString() ?? string.Empty,
|
||||
Composer = track.Composer,
|
||||
Copyright = track.Copyright,
|
||||
Description = track.Comment,
|
||||
DiscNumber = track.DiscNumber,
|
||||
FxName = GetField(track, "ixml.USER.FXNAME"),
|
||||
Genre = track.Genre,
|
||||
Group = track.Group,
|
||||
Keywords = GetField(track, "ixml.USER.KEYWORDS"),
|
||||
Library = GetField(track, "ixml.ASWG.library", "ixml.USER.LIBRARY"),
|
||||
Manufacturer = GetField(track, "ixml.USER.MANUFACTURER"),
|
||||
MicPerspective = GetField(track, "USER.MICPERSPECTIVE"),
|
||||
Notes = GetField(track, "ixml.ASWG.notes", "ixml.USER.NOTES"),
|
||||
Publisher = track.Publisher,
|
||||
Rating = ParseRating(GetField(track, "USER.RATING")),
|
||||
ReleaseDate = track.OriginalReleaseDate,
|
||||
Subcategory = GetField(track, "USER.SUBCATEGORY", "ASWG.subCategory"),
|
||||
TrackNumber = track.TrackNumber,
|
||||
TrackTitle = track.Title,
|
||||
TrackYear = track.Year,
|
||||
|
||||
|
||||
// BWF 特定字段 (通过附加数据获取)
|
||||
CodingHistory = GetBwfField(track, "CodingHistory"),
|
||||
BwfOriginator = GetBwfField(track, "BwfOriginator"),
|
||||
BwfOriginatorRef = GetBwfField(track, "BwfOriginatorRef"),
|
||||
BwfCodingHistory = GetField(track, "bext.coding_history"),
|
||||
BwfOriginator = GetField(track, "bext.originator"),
|
||||
BwfOriginatorRef = GetField(track, "bext.originatorRef"),
|
||||
};
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 BWF 字段值
|
||||
/// 从 AdditionalFields 中按优先级获取字段值。
|
||||
/// 返回第一个存在且非空的 value,全部未命中返回 null。
|
||||
/// </summary>
|
||||
private static string? GetBwfField(Track track, string fieldName)
|
||||
static string? GetField(Track track, params string[] fieldNames)
|
||||
{
|
||||
try
|
||||
if (track.AdditionalFields is null) return null;
|
||||
|
||||
foreach (string name in fieldNames)
|
||||
{
|
||||
if (track.AdditionalFields != null && track.AdditionalFields.TryGetValue(fieldName, out string? field))
|
||||
if (track.AdditionalFields.TryGetValue(name, out string? field)
|
||||
&& !string.IsNullOrEmpty(field))
|
||||
{
|
||||
return field;
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 ATL AdditionalFields 中的 Rating 字符串解析为 uint?
|
||||
/// 支持整数("3")和浮点("0.000000")两种格式
|
||||
/// 输入为空或无法解析时返回 null
|
||||
/// </summary>
|
||||
static uint? ParseRating(string? rawValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawValue))
|
||||
return null;
|
||||
|
||||
if (double.TryParse(rawValue,
|
||||
NumberStyles.Float | NumberStyles.AllowThousands,
|
||||
CultureInfo.InvariantCulture,
|
||||
out double value))
|
||||
{
|
||||
// 忽略 BWF 字段读取错误
|
||||
if (value is < 0 or > uint.MaxValue)
|
||||
return null;
|
||||
|
||||
return (uint)Math.Round(value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -101,13 +138,4 @@ public class AudioMetadataReader
|
||||
byte[] hash = md5.ComputeHash(stream);
|
||||
return Convert.ToHexStringLower(hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成 UUID
|
||||
/// </summary>
|
||||
/// <returns>UUID 字符串</returns>
|
||||
static string GenerateUuid()
|
||||
{
|
||||
return Guid.NewGuid().ToString();
|
||||
}
|
||||
}
|
||||
|
||||
Regular → Executable
+1
-1
@@ -9,7 +9,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.72" />
|
||||
<PackageReference Include="System.Data.SQLite" Version="2.0.3" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.12.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.13.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Regular → Executable
+5
-5
@@ -6,7 +6,7 @@ namespace OCES.Resonance.Core;
|
||||
|
||||
public static class Database
|
||||
{
|
||||
static readonly string DefaultConnectionString = "Data Source=default.db;Version=3;";
|
||||
static readonly string DefaultConnectionString = "Data Source=default.rdb;Version=3;";
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据库连接
|
||||
@@ -15,7 +15,7 @@ public static class Database
|
||||
/// <returns>数据库连接</returns>
|
||||
public static IDbConnection GetConnection(string dbName = "default")
|
||||
{
|
||||
string connectionString = $"Data Source={dbName}.db;Version=3;";
|
||||
string connectionString = $"Data Source={dbName}.rdb;Version=3;";
|
||||
return new SQLiteConnection(connectionString);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public static class Database
|
||||
connection.Open();
|
||||
|
||||
const string sql = @"
|
||||
CREATE TABLE IF NOT EXISTS sounds (
|
||||
CREATE TABLE IF NOT EXISTS audio_files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
unique_id TEXT NOT NULL UNIQUE,
|
||||
short_id TEXT,
|
||||
@@ -144,7 +144,7 @@ public static class Database
|
||||
@BwfOriginator, @BwfOriginatorRef, @ProjectName, @Library, @CdTitle,
|
||||
@TrackTitle, @Episode, @Scene, @Take, @Tape, @CueNumber, @SyncPoint,
|
||||
@ReleaseDate, @TrackYear, @IsEdited, @IsSplit, @Location, @Group,
|
||||
@Markers, @Comments, @Notes, @Copyright, @CodingHistory, @Microphone,
|
||||
@Markers, @Comments, @Notes, @Copyright, @BwfCodingHistory, @Microphone,
|
||||
@MicPerspective, @User1, @User2, @User3, @User4, @User5, @User6, @User7, @User8
|
||||
);
|
||||
";
|
||||
@@ -195,7 +195,7 @@ public static class Database
|
||||
@BwfOriginator, @BwfOriginatorRef, @ProjectName, @Library, @CdTitle,
|
||||
@TrackTitle, @Episode, @Scene, @Take, @Tape, @CueNumber, @SyncPoint,
|
||||
@ReleaseDate, @TrackYear, @IsEdited, @IsSplit, @Location, @Group,
|
||||
@Markers, @Comments, @Notes, @Copyright, @CodingHistory, @Microphone,
|
||||
@Markers, @Comments, @Notes, @Copyright, @BwfCodingHistory, @Microphone,
|
||||
@MicPerspective, @User1, @User2, @User3, @User4, @User5, @User6, @User7, @User8
|
||||
);
|
||||
";
|
||||
|
||||
Reference in New Issue
Block a user