WIP: 扫描扫描指定目录内的所有音频文件,获取其meta信息,并将其添加至一个名为default的数据库。
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
namespace OCES.Resonance.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 音频元数据验证器,验证元数据完整性和格式
|
||||
/// </summary>
|
||||
public class AudioFileMetaValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证必填字段是否完整
|
||||
/// </summary>
|
||||
/// <param name="meta">音频元数据对象</param>
|
||||
/// <param name="errors">验证错误列表</param>
|
||||
/// <returns>是否验证通过</returns>
|
||||
public bool Validate(AudioFileMeta meta, out List<string> errors)
|
||||
{
|
||||
errors = new List<string>();
|
||||
|
||||
if (meta == null)
|
||||
{
|
||||
errors.Add("元数据对象为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证必备字符串字段
|
||||
ValidateRequiredField(meta.UniqueId, "UniqueId", errors);
|
||||
ValidateRequiredField(meta.Md5, "Md5", errors);
|
||||
ValidateRequiredField(meta.Path, "Path", errors);
|
||||
ValidateRequiredField(meta.Filename, "Filename", errors);
|
||||
ValidateRequiredField(meta.Folder, "Folder", errors);
|
||||
ValidateRequiredField(meta.Directory, "Directory", errors);
|
||||
ValidateRequiredField(meta.Type, "Type", errors);
|
||||
|
||||
// 验证数值字段
|
||||
ValidatePositiveValue(meta.Duration, "Duration", errors);
|
||||
ValidatePositiveValue(meta.TotalSamples, "TotalSamples", errors);
|
||||
ValidatePositiveValue(meta.BitDepth, "BitDepth", errors);
|
||||
ValidatePositiveValue(meta.Channels, "Channels", errors);
|
||||
ValidatePositiveValue(meta.SampleRate, "SampleRate", errors);
|
||||
|
||||
// 验证日期字段
|
||||
ValidateRequiredField(meta.DateAdded, "DateAdded", errors);
|
||||
ValidateRequiredField(meta.OriginalModificationDate, "OriginalModificationDate", errors);
|
||||
ValidateRequiredField(meta.OriginationTime, "OriginationTime", errors);
|
||||
|
||||
// 验证文件路径是否存在
|
||||
if (!File.Exists(meta.Path))
|
||||
{
|
||||
errors.Add($"文件不存在:{meta.Path}");
|
||||
}
|
||||
|
||||
// 验证 MD5 格式(32 位十六进制)
|
||||
if (!IsValidMd5(meta.Md5))
|
||||
{
|
||||
errors.Add($"MD5 格式无效:{meta.Md5}");
|
||||
}
|
||||
|
||||
return errors.Count == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证必填字符串字段
|
||||
/// </summary>
|
||||
private static void ValidateRequiredField(string? value, string fieldName, List<string> errors)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
errors.Add($"{fieldName} 不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证必填值类型字段
|
||||
/// </summary>
|
||||
private static void ValidateRequiredField<T>(T value, string fieldName, List<string> errors) where T : struct
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(value, default))
|
||||
{
|
||||
errors.Add($"{fieldName} 不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证正数值
|
||||
/// </summary>
|
||||
private static void ValidatePositiveValue(double value, string fieldName, List<string> errors)
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
errors.Add($"{fieldName} 必须为正数 (当前值:{value})");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证正数整数值
|
||||
/// </summary>
|
||||
private static void ValidatePositiveValue(int value, string fieldName, List<string> errors)
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
errors.Add($"{fieldName} 必须为正数 (当前值:{value})");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证正数无符号整数值
|
||||
/// </summary>
|
||||
private static void ValidatePositiveValue(uint value, string fieldName, List<string> errors)
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
errors.Add($"{fieldName} 必须为正数 (当前值:{value})");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 MD5 格式是否有效
|
||||
/// </summary>
|
||||
private static bool IsValidMd5(string md5)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(md5) || md5.Length != 32)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return md5.All(c => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
namespace OCES.Resonance.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 音频文件扫描器,负责递归扫描目录并识别音频文件
|
||||
/// </summary>
|
||||
public class AudioFileScanner
|
||||
{
|
||||
private static readonly string[] SupportedExtensions =
|
||||
{
|
||||
".wav", ".mp3", ".flac", ".aiff", ".aif", ".m4a", ".ogg", ".wma", ".bwf", ".wav64"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 扫描指定目录,返回所有音频文件路径
|
||||
/// </summary>
|
||||
/// <param name="directoryPath">要扫描的目录路径</param>
|
||||
/// <param name="recursive">是否递归扫描子目录</param>
|
||||
/// <returns>音频文件路径集合</returns>
|
||||
public IEnumerable<string> ScanDirectory(string directoryPath, bool recursive = true)
|
||||
{
|
||||
if (!Directory.Exists(directoryPath))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"目录不存在:{directoryPath}");
|
||||
}
|
||||
|
||||
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
|
||||
return SupportedExtensions
|
||||
.SelectMany(ext => Directory.EnumerateFiles(directoryPath, $"*{ext}", searchOption))
|
||||
.Where(IsSupportedAudioFile)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断文件是否为支持的音频格式
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <returns>是否为支持的音频格式</returns>
|
||||
public bool IsSupportedAudioFile(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(filePath).ToLowerInvariant();
|
||||
return SupportedExtensions.Contains(extension);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
namespace OCES.Resonance.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 音频库管理服务,协调扫描、读取和入库的完整流程
|
||||
/// </summary>
|
||||
public class AudioLibraryService
|
||||
{
|
||||
private readonly AudioFileScanner _scanner;
|
||||
private readonly AudioMetadataReader _metadataReader;
|
||||
|
||||
public AudioLibraryService()
|
||||
{
|
||||
_scanner = new AudioFileScanner();
|
||||
_metadataReader = new AudioMetadataReader();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 扫描目录并将结果添加到数据库
|
||||
/// </summary>
|
||||
/// <param name="directoryPath">要扫描的目录路径</param>
|
||||
/// <param name="databaseName">数据库名称(不含扩展名)</param>
|
||||
/// <param name="skipExisting">是否跳过已存在的文件</param>
|
||||
/// <returns>扫描结果统计</returns>
|
||||
public async Task<ScanResult> ScanAndImportToLibrary(
|
||||
string directoryPath,
|
||||
string databaseName = "default",
|
||||
bool skipExisting = true)
|
||||
{
|
||||
var result = new ScanResult
|
||||
{
|
||||
TotalFiles = 0,
|
||||
SuccessCount = 0,
|
||||
SkipCount = 0,
|
||||
ErrorCount = 0,
|
||||
Errors = new List<string>()
|
||||
};
|
||||
|
||||
// 初始化数据库
|
||||
Database.InitializeDatabase();
|
||||
|
||||
// 扫描所有音频文件
|
||||
var audioFiles = _scanner.ScanDirectory(directoryPath).ToList();
|
||||
result.TotalFiles = audioFiles.Count;
|
||||
|
||||
if (audioFiles.Count == 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var metadataList = new List<AudioFileMeta>();
|
||||
|
||||
// 读取每个文件的元数据
|
||||
foreach (var filePath in audioFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 计算 MD5 用于查重
|
||||
var md5 = CalculateMd5ForFile(filePath);
|
||||
|
||||
// 检查是否已存在
|
||||
if (skipExisting && Database.EntryExists(md5, filePath))
|
||||
{
|
||||
result.SkipCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 读取元数据
|
||||
var metadata = _metadataReader.ReadMetadata(filePath);
|
||||
metadataList.Add(metadata);
|
||||
result.SuccessCount++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.ErrorCount++;
|
||||
result.Errors.Add($"读取失败 {filePath}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// 批量入库
|
||||
if (metadataList.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
Database.AddEntries(metadataList);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Errors.Add($"批量入库失败:{ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return await Task.FromResult(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算文件 MD5(用于查重,不创建完整 AudioFileMeta 对象)
|
||||
/// </summary>
|
||||
private static string CalculateMd5ForFile(string filePath)
|
||||
{
|
||||
using var md5 = System.Security.Cryptography.MD5.Create();
|
||||
using var stream = File.OpenRead(filePath);
|
||||
var hash = md5.ComputeHash(stream);
|
||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 扫描结果统计
|
||||
/// </summary>
|
||||
public class ScanResult
|
||||
{
|
||||
/// <summary>扫描到的文件总数</summary>
|
||||
public int TotalFiles { get; set; }
|
||||
|
||||
/// <summary>成功入库的文件数</summary>
|
||||
public int SuccessCount { get; set; }
|
||||
|
||||
/// <summary>跳过的文件数(已存在)</summary>
|
||||
public int SkipCount { get; set; }
|
||||
|
||||
/// <summary>读取失败的文件数</summary>
|
||||
public int ErrorCount { get; set; }
|
||||
|
||||
/// <summary>错误详情列表</summary>
|
||||
public List<string> Errors { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取人类可读的统计报告
|
||||
/// </summary>
|
||||
public string GetSummary()
|
||||
{
|
||||
return $"扫描完成:共 {TotalFiles} 个文件,成功 {SuccessCount} 个,跳过 {SkipCount} 个,失败 {ErrorCount} 个";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using System.Security.Cryptography;
|
||||
using ATL;
|
||||
|
||||
namespace OCES.Resonance.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 音频元数据读取器,使用 ATL 库读取音频文件的技术参数和元数据
|
||||
/// </summary>
|
||||
public class AudioMetadataReader
|
||||
{
|
||||
/// <summary>
|
||||
/// 从音频文件读取元数据
|
||||
/// </summary>
|
||||
/// <param name="filePath">音频文件路径</param>
|
||||
/// <returns>音频元数据对象</returns>
|
||||
public AudioFileMeta ReadMetadata(string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException($"文件不存在:{filePath}");
|
||||
}
|
||||
|
||||
var track = new Track(filePath);
|
||||
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
|
||||
var meta = new AudioFileMeta
|
||||
{
|
||||
// 必备条目
|
||||
Id = 0, // 自增 ID,由数据库生成
|
||||
UniqueId = uniqueId,
|
||||
Md5 = md5,
|
||||
Path = Path.GetFullPath(filePath),
|
||||
Filename = fileName,
|
||||
Folder = folderName,
|
||||
Directory = directory,
|
||||
Duration = durationSeconds,
|
||||
TotalSamples = totalSamples,
|
||||
BitDepth = bitDepth,
|
||||
Channels = channels,
|
||||
SampleRate = (int)sampleRate,
|
||||
Type = fileType,
|
||||
DateAdded = DateTime.Now,
|
||||
OriginalModificationDate = lastWriteTime,
|
||||
OriginationTime = createTime,
|
||||
|
||||
// 可选条目
|
||||
Bpm = track.BPM > 0 ? track.BPM : null,
|
||||
Description = track.Description,
|
||||
Genre = track.Genre,
|
||||
Artist = track.Artist,
|
||||
Composer = track.Composer,
|
||||
Publisher = track.Publisher,
|
||||
Copyright = track.Copyright,
|
||||
|
||||
// BWF 特定字段 (通过附加数据获取)
|
||||
CodingHistory = GetBwfField(track, "CodingHistory"),
|
||||
Originator = GetBwfField(track, "Originator"),
|
||||
OriginatorRef = GetBwfField(track, "OriginatorRef"),
|
||||
};
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 BWF 字段值
|
||||
/// </summary>
|
||||
private static string? GetBwfField(Track track, string fieldName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (track.AdditionalFields != null && track.AdditionalFields.TryGetValue(fieldName, out string? field))
|
||||
{
|
||||
return field;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略 BWF 字段读取错误
|
||||
}
|
||||
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>
|
||||
/// 计算文件 MD5 值
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <returns>MD5 哈希值(十六进制字符串)</returns>
|
||||
private static string CalculateMd5(string filePath)
|
||||
{
|
||||
using var md5 = MD5.Create();
|
||||
using var stream = File.OpenRead(filePath);
|
||||
|
||||
var hash = md5.ComputeHash(stream);
|
||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成 UUID
|
||||
/// </summary>
|
||||
/// <returns>UUID 字符串</returns>
|
||||
private static string GenerateUuid()
|
||||
{
|
||||
return Guid.NewGuid().ToString("N"); // 无连字符的 UUID
|
||||
}
|
||||
}
|
||||
+231
-1
@@ -1,9 +1,239 @@
|
||||
using System.Data;
|
||||
using System.Data.SQLite;
|
||||
using Dapper;
|
||||
|
||||
namespace OCES.Resonance.Core;
|
||||
|
||||
public static class Database
|
||||
{
|
||||
internal static bool AddEntry()
|
||||
static readonly string DefaultConnectionString = "Data Source=default.db;Version=3;";
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据库连接
|
||||
/// </summary>
|
||||
/// <param name="dbName">数据库名称(不含扩展名)</param>
|
||||
/// <returns>数据库连接</returns>
|
||||
public static IDbConnection GetConnection(string dbName = "default")
|
||||
{
|
||||
var connectionString = $"Data Source={dbName}.db;Version=3;";
|
||||
return new SQLiteConnection(connectionString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化数据库表结构
|
||||
/// </summary>
|
||||
public static void InitializeDatabase()
|
||||
{
|
||||
using var connection = GetConnection();
|
||||
connection.Open();
|
||||
|
||||
const string sql = @"
|
||||
CREATE TABLE IF NOT EXISTS audio_files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
unique_id TEXT NOT NULL UNIQUE,
|
||||
short_id TEXT,
|
||||
md5 TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
folder TEXT NOT NULL,
|
||||
directory TEXT NOT NULL,
|
||||
duration REAL NOT NULL,
|
||||
total_samples INTEGER NOT NULL,
|
||||
bit_depth INTEGER NOT NULL,
|
||||
channels INTEGER NOT NULL,
|
||||
sample_rate INTEGER NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
date_added TEXT NOT NULL,
|
||||
original_modification_date TEXT NOT NULL,
|
||||
origination_time TEXT NOT NULL,
|
||||
|
||||
bpm REAL,
|
||||
frame_rate TEXT,
|
||||
timecode INTEGER,
|
||||
description TEXT,
|
||||
category TEXT,
|
||||
subcategory TEXT,
|
||||
cat_id TEXT,
|
||||
category_full TEXT,
|
||||
genre TEXT,
|
||||
style TEXT,
|
||||
mood TEXT,
|
||||
keywords TEXT,
|
||||
rating INTEGER,
|
||||
artist TEXT,
|
||||
composer TEXT,
|
||||
designer TEXT,
|
||||
recordist TEXT,
|
||||
publisher TEXT,
|
||||
manufacturer TEXT,
|
||||
originator TEXT,
|
||||
originator_ref TEXT,
|
||||
project_name TEXT,
|
||||
library TEXT,
|
||||
cd_title TEXT,
|
||||
track_title TEXT,
|
||||
episode TEXT,
|
||||
scene TEXT,
|
||||
take TEXT,
|
||||
tape TEXT,
|
||||
cue_number INTEGER,
|
||||
sync_point INTEGER,
|
||||
release_date TEXT,
|
||||
track_year TEXT,
|
||||
is_edited INTEGER,
|
||||
is_split INTEGER,
|
||||
location TEXT,
|
||||
group TEXT,
|
||||
markers TEXT,
|
||||
comments TEXT,
|
||||
notes TEXT,
|
||||
copyright TEXT,
|
||||
coding_history TEXT,
|
||||
microphone TEXT,
|
||||
mic_perspective TEXT,
|
||||
user1 TEXT,
|
||||
user2 TEXT,
|
||||
user3 TEXT,
|
||||
user4 TEXT,
|
||||
user5 TEXT,
|
||||
user6 TEXT,
|
||||
user7 TEXT,
|
||||
user8 TEXT,
|
||||
|
||||
INDEX idx_md5 (md5),
|
||||
INDEX idx_path (path),
|
||||
INDEX idx_unique_id (unique_id)
|
||||
);
|
||||
";
|
||||
|
||||
connection.Execute(sql);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加单条音频记录
|
||||
/// </summary>
|
||||
/// <param name="meta">音频元数据</param>
|
||||
/// <returns>是否添加成功</returns>
|
||||
public static bool AddEntry(AudioFileMeta meta)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var connection = GetConnection();
|
||||
connection.Open();
|
||||
|
||||
const string sql = @"
|
||||
INSERT INTO audio_files (
|
||||
unique_id, short_id, md5, path, filename, folder, directory,
|
||||
duration, total_samples, bit_depth, channels, sample_rate, type,
|
||||
date_added, original_modification_date, origination_time,
|
||||
bpm, frame_rate, timecode, description, category, subcategory,
|
||||
cat_id, category_full, genre, style, mood, keywords, rating,
|
||||
artist, composer, designer, recordist, publisher, manufacturer,
|
||||
originator, originator_ref, project_name, library, cd_title,
|
||||
track_title, episode, scene, take, tape, cue_number, sync_point,
|
||||
release_date, track_year, is_edited, is_split, location, group,
|
||||
markers, comments, notes, copyright, coding_history, microphone,
|
||||
mic_perspective, user1, user2, user3, user4, user5, user6, user7, user8
|
||||
) VALUES (
|
||||
@UniqueId, @ShortId, @Md5, @Path, @Filename, @Folder, @Directory,
|
||||
@Duration, @TotalSamples, @BitDepth, @Channels, @SampleRate, @Type,
|
||||
@DateAdded, @OriginalModificationDate, @OriginationTime,
|
||||
@Bpm, @FrameRate, @Timecode, @Description, @Category, @Subcategory,
|
||||
@CatId, @CategoryFull, @Genre, @Style, @Mood, @Keywords, @Rating,
|
||||
@Artist, @Composer, @Designer, @Recordist, @Publisher, @Manufacturer,
|
||||
@Originator, @OriginatorRef, @ProjectName, @Library, @CdTitle,
|
||||
@TrackTitle, @Episode, @Scene, @Take, @Tape, @CueNumber, @SyncPoint,
|
||||
@ReleaseDate, @TrackYear, @IsEdited, @IsSplit, @Location, @Group,
|
||||
@Markers, @Comments, @Notes, @Copyright, @CodingHistory, @Microphone,
|
||||
@MicPerspective, @User1, @User2, @User3, @User4, @User5, @User6, @User7, @User8
|
||||
);
|
||||
";
|
||||
|
||||
connection.Execute(sql, meta);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量添加音频记录
|
||||
/// </summary>
|
||||
/// <param name="entries">音频元数据列表</param>
|
||||
/// <returns>成功添加的记录数</returns>
|
||||
public static int AddEntries(IEnumerable<AudioFileMeta> entries)
|
||||
{
|
||||
var count = 0;
|
||||
using var connection = GetConnection();
|
||||
connection.Open();
|
||||
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
const string sql = @"
|
||||
INSERT INTO audio_files (
|
||||
unique_id, short_id, md5, path, filename, folder, directory,
|
||||
duration, total_samples, bit_depth, channels, sample_rate, type,
|
||||
date_added, original_modification_date, origination_time,
|
||||
bpm, frame_rate, timecode, description, category, subcategory,
|
||||
cat_id, category_full, genre, style, mood, keywords, rating,
|
||||
artist, composer, designer, recordist, publisher, manufacturer,
|
||||
originator, originator_ref, project_name, library, cd_title,
|
||||
track_title, episode, scene, take, tape, cue_number, sync_point,
|
||||
release_date, track_year, is_edited, is_split, location, group,
|
||||
markers, comments, notes, copyright, coding_history, microphone,
|
||||
mic_perspective, user1, user2, user3, user4, user5, user6, user7, user8
|
||||
) VALUES (
|
||||
@UniqueId, @ShortId, @Md5, @Path, @Filename, @Folder, @Directory,
|
||||
@Duration, @TotalSamples, @BitDepth, @Channels, @SampleRate, @Type,
|
||||
@DateAdded, @OriginalModificationDate, @OriginationTime,
|
||||
@Bpm, @FrameRate, @Timecode, @Description, @Category, @Subcategory,
|
||||
@CatId, @CategoryFull, @Genre, @Style, @Mood, @Keywords, @Rating,
|
||||
@Artist, @Composer, @Designer, @Recordist, @Publisher, @Manufacturer,
|
||||
@Originator, @OriginatorRef, @ProjectName, @Library, @CdTitle,
|
||||
@TrackTitle, @Episode, @Scene, @Take, @Tape, @CueNumber, @SyncPoint,
|
||||
@ReleaseDate, @TrackYear, @IsEdited, @IsSplit, @Location, @Group,
|
||||
@Markers, @Comments, @Notes, @Copyright, @CodingHistory, @Microphone,
|
||||
@MicPerspective, @User1, @User2, @User3, @User4, @User5, @User6, @User7, @User8
|
||||
);
|
||||
";
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
connection.Execute(sql, entry, transaction);
|
||||
count++;
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查记录是否已存在
|
||||
/// </summary>
|
||||
/// <param name="md5">文件 MD5 值</param>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <returns>是否存在</returns>
|
||||
public static bool EntryExists(string md5, string filePath)
|
||||
{
|
||||
using var connection = GetConnection();
|
||||
connection.Open();
|
||||
|
||||
const string sql = @"
|
||||
SELECT COUNT(*) FROM audio_files
|
||||
WHERE md5 = @Md5 OR path = @Path;
|
||||
";
|
||||
|
||||
var count = connection.ExecuteScalar<int>(sql, new { Md5 = md5, Path = filePath });
|
||||
return count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user