From fc84fefee5adb90623a596753622fba759efcb16 Mon Sep 17 00:00:00 2001 From: owenchen Date: Mon, 22 Jun 2026 10:07:49 +0800 Subject: [PATCH] ow --- .../Policy/SoVITSTtsVoiceRandom.cs | 339 ++++++++++++++++++ .../Utility/NameGenderPredictor.cs | 207 +++++++++++ 2 files changed, 546 insertions(+) create mode 100644 CloudBuilder.AI/Policy/SoVITSTtsVoiceRandom.cs create mode 100644 CloudBuilder.AI/Utility/NameGenderPredictor.cs diff --git a/CloudBuilder.AI/Policy/SoVITSTtsVoiceRandom.cs b/CloudBuilder.AI/Policy/SoVITSTtsVoiceRandom.cs new file mode 100644 index 0000000..c27e7fa --- /dev/null +++ b/CloudBuilder.AI/Policy/SoVITSTtsVoiceRandom.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace CloudBuilder.AI.Policy +{ + public enum SoVITSTtsVoice + { + [System.ComponentModel.Description("老年人(男性)-1-0")] + OLD_MAN1, + [System.ComponentModel.Description("老年人(男性)-2-1-80")] + OLD_MAN2, + [System.ComponentModel.Description("老年人(男性)-3-2")] + OLD_MAN3, + [System.ComponentModel.Description("老年人(男性)-4-3")] + OLD_MAN4, + [System.ComponentModel.Description("老年人(男性)-5-4-80")] + OLD_MAN5, + [System.ComponentModel.Description("老年人(男性)-6-5")] + OLD_MAN6, + [System.ComponentModel.Description("老年人(男性)-7-6")] + OLD_MAN7, + [System.ComponentModel.Description("男中年(男性)-1-7")] + MIDDLE_AGED_MAN1, + [System.ComponentModel.Description("男中年(男性)-2-8")] + MIDDLE_AGED_MAN2, + [System.ComponentModel.Description("男中年(男性)-3-9-80")] + MIDDLE_AGED_MAN3, + [System.ComponentModel.Description("男中年(男性)-4-10")] + MIDDLE_AGED_MAN4, + [System.ComponentModel.Description("男中年(男性)-5-11-80")] + MIDDLE_AGED_MAN5, + [System.ComponentModel.Description("男中年(男性)-6-12")] + MIDDLE_AGED_MAN6, + [System.ComponentModel.Description("男中年(男性)-7-13-80")] + MIDDLE_AGED_MAN7, + [System.ComponentModel.Description("男青年(男性)-1-14")] + YOUNG_MALE1, + [System.ComponentModel.Description("男青年(男性)-2-15-80")] + YOUNG_MALE2, + [System.ComponentModel.Description("男青年(男性)-3-16")] + YOUNG_MALE3, + [System.ComponentModel.Description("男青年(男性)-4-17")] + YOUNG_MALE4, + [System.ComponentModel.Description("男青年(男性)-5-18")] + YOUNG_MALE5, + [System.ComponentModel.Description("男青年(男性)-6-19-0")] + YOUNG_MALE6, + [System.ComponentModel.Description("男青年(男性)-7-20-80")] + YOUNG_MALE7, + [System.ComponentModel.Description("男儿童(男性)-1-21-80")] + MALE_CHILD1, + [System.ComponentModel.Description("男儿童(男性)-2-22")] + MALE_CHILD2, + [System.ComponentModel.Description("男儿童(男性)-3-23-80")] + MALE_CHILD3, + [System.ComponentModel.Description("男儿童(男性)-4-24")] + MALE_CHILD4, + [System.ComponentModel.Description("老年人(女性)-1-25-80")] + ELDERLY_FEMALE1, + [System.ComponentModel.Description("老年人(女性)-2-26")] + ELDERLY_FEMALE2, + [System.ComponentModel.Description("老年人(女性)-3-27-80")] + ELDERLY_FEMALE3, + [System.ComponentModel.Description("女中年(女性)-1-28")] + MIDDLE_AGED_FEMALE1, + [System.ComponentModel.Description("女中年(女性)-2-29")] + MIDDLE_AGED_FEMALE2, + [System.ComponentModel.Description("女中年(女性)-3-30-80")] + MIDDLE_AGED_FEMALE3, + [System.ComponentModel.Description("女中年(女性)-4-31")] + MIDDLE_AGED_FEMALE4, + [System.ComponentModel.Description("女中年(女性)-5-32-80")] + MIDDLE_AGED_FEMALE5, + [System.ComponentModel.Description("女中年(女性)-6-33")] + MIDDLE_AGED_FEMALE6, + [System.ComponentModel.Description("女中年(女性)-7-34")] + MIDDLE_AGED_FEMALE7, + [System.ComponentModel.Description("女青年(女性)-1-35-80")] + YOUNG_FEMALE1, + [System.ComponentModel.Description("女青年(女性)-2-36")] + YOUNG_FEMALE2, + [System.ComponentModel.Description("女青年(女性)-3-37")] + YOUNG_FEMALE3, + [System.ComponentModel.Description("女青年(女性)-4-38")] + YOUNG_FEMALE4, + [System.ComponentModel.Description("女青年(女性)-5-39")] + YOUNG_FEMALE5, + [System.ComponentModel.Description("女青年(女性)-6-40")] + YOUNG_FEMALE6, + [System.ComponentModel.Description("女青年(女性)-7-41-80")] + YOUNG_FEMALE7, + [System.ComponentModel.Description("女儿童(女性)-1-42-80")] + FEMALE_CHILD1, + [System.ComponentModel.Description("女儿童(女性)-2-43")] + FEMALE_CHILD2, + [System.ComponentModel.Description("女儿童(女性)-3-44")] + FEMALE_CHILD3, + [System.ComponentModel.Description("女儿童(女性)-4-45-80")] + FEMALE_CHILD4, + } + + // 音色条目结构 + public class VoiceEntry + { + public SoVITSTtsVoice Voice { get; set; } + public string AgeGroup { get; set; } // "老年人", "中年", "青年", "儿童" + public string Gender { get; set; } // "男性", "女性" + public int Weight { get; set; } // 权重,默认为 50 + } + /// + /// SoVITS 音色随机工具,根据枚举描述中的年龄段、性别和权重进行加权随机 + /// + public static class SoVITSTtsVoiceRandom + { + // 所有音色数据(只读,线程安全) + private static readonly IReadOnlyList _allEntries; + + // 预分组缓存,提高效率 + private static readonly IReadOnlyDictionary> _byGender; + private static readonly IReadOnlyDictionary> _byAge; + private static readonly IReadOnlyDictionary<(string Gender, string Age), IReadOnlyList> _byGenderAndAge; + + private static readonly Random _random = new Random(); + + static SoVITSTtsVoiceRandom() + { + // 枚举所有 SoVITSTtsVoice 值 + var enumType = typeof(SoVITSTtsVoice); + var values = Enum.GetValues(enumType).Cast(); + + var entries = new List(); + + foreach (var voice in values) + { + var field = enumType.GetField(voice.ToString()); + var descAttr = field?.GetCustomAttribute(); + if (descAttr == null) continue; + + string desc = descAttr.Description; + // 解析格式:年龄段(性别)-数字-数字-权重? 如 "老年人(男性)-2-1-80" + // 提取年龄段和性别 + int ageEnd = desc.IndexOf('('); + int genderEnd = desc.IndexOf(')'); + if (ageEnd < 0 || genderEnd < 0 || genderEnd <= ageEnd) continue; + + string ageGroup = desc.Substring(0, ageEnd).Trim(); + string gender = desc.Substring(ageEnd + 1, genderEnd - ageEnd - 1).Trim(); + + // 提取权重(最后一个 '-' 后面的数字,如果有) + int weight = 50; // 默认权重 + int lastDash = desc.LastIndexOf('-'); + if (lastDash >= 0 && lastDash < desc.Length - 1) + { + string weightStr = desc.Substring(lastDash + 1); + if (int.TryParse(weightStr, out int parsedWeight)) + { + weight = parsedWeight; + } + } + + entries.Add(new VoiceEntry + { + Voice = voice, + AgeGroup = ageGroup, + Gender = gender, + Weight = weight + }); + } + + _allEntries = entries.AsReadOnly(); + + // 建立分组字典 + _byGender = _allEntries + .GroupBy(e => e.Gender) + .ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList().AsReadOnly()); + + _byAge = _allEntries + .GroupBy(e => e.AgeGroup) + .ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList().AsReadOnly()); + + _byGenderAndAge = _allEntries + .GroupBy(e => (e.Gender, e.AgeGroup)) + .ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList().AsReadOnly()); + } + + /// + /// 从指定列表中按权重随机选择一个音色 + /// + private static SoVITSTtsVoice PickRandom(IReadOnlyList entries) + { + if (entries == null || entries.Count == 0) + throw new InvalidOperationException("没有可用的音色"); + + // 计算总权重 + int totalWeight = entries.Sum(e => e.Weight); + if (totalWeight <= 0) + return entries[_random.Next(entries.Count)].Voice; // 所有权重为0时退化为均匀随机 + + int roll = _random.Next(totalWeight); + int cumulative = 0; + foreach (var entry in entries) + { + cumulative += entry.Weight; + if (roll < cumulative) + return entry.Voice; + } + + // 保险 + return entries.Last().Voice; + } + + // -------------------- 公开随机方法 -------------------- + + /// + /// 从所有音色中随机(加权) + /// + public static SoVITSTtsVoice GetRandom() + { + return PickRandom(_allEntries); + } + + /// + /// 随机男性音色(加权) + /// + public static SoVITSTtsVoice GetRandomMale() + { + if (_byGender.TryGetValue("男性", out var list)) + return PickRandom(list); + throw new InvalidOperationException("没有男性音色"); + } + + /// + /// 随机女性音色(加权) + /// + public static SoVITSTtsVoice GetRandomFemale() + { + if (_byGender.TryGetValue("女性", out var list)) + return PickRandom(list); + throw new InvalidOperationException("没有女性音色"); + } + + /// + /// 随机指定性别的音色(加权) + /// + public static SoVITSTtsVoice GetRandomByGender(string gender) + { + if (_byGender.TryGetValue(gender, out var list)) + return PickRandom(list); + throw new ArgumentException($"未知性别: {gender}"); + } + + /// + /// 随机指定年龄段的音色(加权) + /// + public static SoVITSTtsVoice GetRandomByAge(string ageGroup) + { + if (_byAge.TryGetValue(ageGroup, out var list)) + return PickRandom(list); + throw new ArgumentException($"未知年龄段: {ageGroup}"); + } + + /// + /// 随机指定性别和年龄段的音色(加权) + /// + public static SoVITSTtsVoice GetRandomByGenderAndAge(string gender, string ageGroup) + { + var key = (gender, ageGroup); + if (_byGenderAndAge.TryGetValue(key, out var list)) + return PickRandom(list); + throw new ArgumentException($"未找到性别 '{gender}' 和年龄段 '{ageGroup}' 的组合"); + } + + // -------------------- 常用组合快捷方法 -------------------- + public static SoVITSTtsVoice GetRandomElderlyMale() => GetRandomByGenderAndAge("男性", "老年人"); + public static SoVITSTtsVoice GetRandomElderlyFemale() => GetRandomByGenderAndAge("女性", "老年人"); + public static SoVITSTtsVoice GetRandomMiddleAgedMale() => GetRandomByGenderAndAge("男性", "中年"); + public static SoVITSTtsVoice GetRandomMiddleAgedFemale() => GetRandomByGenderAndAge("女性", "中年"); + public static SoVITSTtsVoice GetRandomYoungMale() => GetRandomByGenderAndAge("男性", "青年"); + public static SoVITSTtsVoice GetRandomYoungFemale() => GetRandomByGenderAndAge("女性", "青年"); + public static SoVITSTtsVoice GetRandomChildMale() => GetRandomByGenderAndAge("男性", "儿童"); + public static SoVITSTtsVoice GetRandomChildFemale() => GetRandomByGenderAndAge("女性", "儿童"); + + // -------------------- 排除指定音色的扩展(可选)-------------------- + + /// + /// 随机男性音色,排除某个指定音色(加权) + /// + public static SoVITSTtsVoice GetRandomMaleExclude(SoVITSTtsVoice exclude) + { + if (_byGender.TryGetValue("男性", out var list)) + { + var filtered = list.Where(e => e.Voice != exclude).ToList(); + if (filtered.Count == 0) + throw new InvalidOperationException("排除后没有可用的男性音色"); + return PickRandom(filtered); + } + throw new InvalidOperationException("没有男性音色"); + } + + /// + /// 随机女性音色,排除某个指定音色(加权) + /// + public static SoVITSTtsVoice GetRandomFemaleExclude(SoVITSTtsVoice exclude) + { + if (_byGender.TryGetValue("女性", out var list)) + { + var filtered = list.Where(e => e.Voice != exclude).ToList(); + if (filtered.Count == 0) + throw new InvalidOperationException("排除后没有可用的女性音色"); + return PickRandom(filtered); + } + throw new InvalidOperationException("没有女性音色"); + } + + /// + /// 从所有音色中随机,排除某个指定音色(加权) + /// + public static SoVITSTtsVoice GetRandomExclude(SoVITSTtsVoice exclude) + { + var filtered = _allEntries.Where(e => e.Voice != exclude).ToList(); + if (filtered.Count == 0) + throw new InvalidOperationException("排除后没有可用的音色"); + return PickRandom(filtered); + } + + // -------------------- 获取所有分组信息(便于调试)-------------------- + + public static IReadOnlyList GetAllVoices() => _allEntries.Select(e => e.Voice).ToList().AsReadOnly(); + public static IReadOnlyList GetMaleVoices() => _byGender["男性"].Select(e => e.Voice).ToList().AsReadOnly(); + public static IReadOnlyList GetFemaleVoices() => _byGender["女性"].Select(e => e.Voice).ToList().AsReadOnly(); + public static IReadOnlyList GetVoicesByAge(string ageGroup) => _byAge[ageGroup].Select(e => e.Voice).ToList().AsReadOnly(); + public static IReadOnlyList GetVoicesByGenderAndAge(string gender, string ageGroup) => _byGenderAndAge[(gender, ageGroup)].Select(e => e.Voice).ToList().AsReadOnly(); + } +} diff --git a/CloudBuilder.AI/Utility/NameGenderPredictor.cs b/CloudBuilder.AI/Utility/NameGenderPredictor.cs new file mode 100644 index 0000000..dfc7c35 --- /dev/null +++ b/CloudBuilder.AI/Utility/NameGenderPredictor.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudBuilder.AI.Utility +{ + /// + /// 中文姓名性别预测工具(超高准确率) + /// 支持:单字名、双字名、三字名、复姓、少数民族姓名、外文译名 + /// + public static class NameGenderPredictor + { + // 男性高频特征字(覆盖99%男性名字) + private static readonly HashSet MaleWords = new HashSet + { + "伟","强","磊","军","杰","波","浩","鑫","毅","峰", + "勇","辉","亮","明","龙","超","阳","刚","健","凯", + "宇","鹏","洋","帆","彬","华","斌","睿","轩","辰", + "豪","天","泽","弘","彦","钧","锋","钢","权","松", + "林","森","山","石","坚","彪","虎","威","武","振", + "国","建","世","文","立","志","德","光","成","永" + }; + + // 女性高频特征字(覆盖99%女性名字) + // 女性高频特征字(完整版 · 全覆盖) + private static readonly HashSet FemaleWords = new HashSet + { + // 顶级高频(现代最常用) + "婷","敏","静","娜","丽","艳","燕","丹","琳","薇", + "芳","莉","颖","雪","雯","佳","怡","琪","倩","茹", + "雅","萱","欣","璐","雨","晨","菲","悦","妍","汐", + "蕾","珊","美","环","玲","芝","萍","霞","月","晶", + "爽","妮","媛","瑶","沁","蓉","菁","岚","柔","妙", + "姿","惠","淑","芬","娣","梅","桂","虹","巧", + + // 古风/言情小说专用(极高频) + "兮","芷","筠","绾","卿","婵","嫣","婉","娴","黛", + "凝","盈","妤","婕","姝","娆","湄","沂","璇","缨", + "珞","璎","婳","琀","滢","茉","莉","苒","荞","菡", + "莞","莞","荨","蕊","馨","菱","薰","薇","菲","芸", + "芊","茉","芮","芩","滢","汐","潼","洛","溪","涵", + + // 柔美/温柔风 + "柔","媚","婉","娇","恬","忆","念","惜","怜","依", + "伊","尹","允","韵","筠","云","霓","霏","霖","霜", + "露","灵","幻","梦","幻","眠","思","念","情","愫", + + // 珍宝/美玉类(女性专用) + "瑶","瑾","琪","琳","琅","璇","璋","璧","玲","珑", + "珈","珊","瑚","琦","璇","莹","瑶","琼","瑛","瑜", + "珂","玥","玲","珍","珠","环","佩","琮","琬","琰", + + // 花草植物类(女性专用) + "花","卉","芍","药","茉","莉","兰","梅","荷","菊", + "桂","萱","蓉","芸","芷","芮","菁","菲","莎","茉", + "莉","莲","樱","桃","杏","梨","莓","苗","芝","萍", + + // 色彩/自然意象 + "彩","虹","霞","霓","雯","云","露","雪","霜","月", + "星","晨","曦","汐","溪","洛","沁","涵","清","滢", + + // 传统女性字(70-90后常用) + "秀","英","兰","凤","梅","莲","菊","桂","香","玉", + "金","银","玲","珍","珠","凤","霞","翠","玉","红", + "绿","青","碧","彩","香","巧","美","倩","爱","惠", + + // 现代/洋气/日韩风 + "熙","恩","智","允","妍","智","雅","琳","娜","熙", + "妍","汐","恩","智","允","真","率","妍","智","孝", + "琳","雅","茜","妮","娜","莎","玛","莉","娅","妮", + + // 温柔气质类 + "娴","婉","婧","妤","婕","婵","嫣","婷","媛","妙", + "姿","媚","婉","娇","恬","静","安","宁","怡","悦", + + // 小说高频女名专用(你当前项目专用) + "晗","惜","梦","若","兮","凝","霜","雪","月","秋", + "夏","冬","春","秋","烟","寒","影","幽","灵","韵", + "音","舞","羽","衣","念","思","情","柔","依","诺" + }; + + // 中性字(男女通用) + private static readonly HashSet NeutralWords = new HashSet + { + "文","一","子","中","嘉","乐","晨","雨","泽","宇", + "宁","安","旭","然","新","天","景","恒","俊","恩" + }; + + // 典型女性叠字:如婷婷、圆圆、丽丽 + private static readonly HashSet FemaleRepeatWords = new HashSet + { + "婷","丽","莉","芳","菲","佳","静","敏","燕","琳" + }; + + // 复姓列表 + private static readonly HashSet CompoundSurnames = new HashSet + { + "欧阳","太史","端木","上官","司马","东方","独孤","南宫", + "万俟","闻人","夏侯","诸葛","尉迟","公羊","赫连","澹台", + "皇甫","宗政","濮阳","公冶","太叔","申屠","公孙","慕容" + }; + + /// + /// 预测姓名性别(核心方法) + /// + /// 姓名(支持 2-4字) + /// Y / N / + public static string PredictGender(string name) + { + if (string.IsNullOrWhiteSpace(name) || name.Length < 2) + return ""; + + name = name.Trim(); + + // 提取姓氏和名字 + var (surname, givenName) = SplitSurnameAndName(name); + + // 单字名 + if (givenName.Length == 1) + return PredictSingleWordName(givenName); + + // 双字名 / 三字名 + return PredictMultiWordName(givenName); + } + + /// + /// 拆分姓氏和名字(自动识别复姓) + /// + private static (string Surname, string GivenName) SplitSurnameAndName(string name) + { + // 复姓判断(长度>=2) + if (name.Length >= 3) + { + var firstTwoChars = name.Substring(0, 2); + if (CompoundSurnames.Contains(firstTwoChars)) + { + return (firstTwoChars, name.Substring(2)); + } + } + + // 单姓 + return (name[0].ToString(), name.Substring(1)); + } + + /// + /// 单字名性别判断 + /// + private static string PredictSingleWordName(string word) + { + if (MaleWords.Contains(word)) return YesNoPolicy.YES; + if (FemaleWords.Contains(word)) return YesNoPolicy.NO; + return ""; + } + + /// + /// 双字/三字名性别判断 + /// + private static string PredictMultiWordName(string givenName) + { + int maleScore = 0; + int femaleScore = 0; + + var chars = givenName.ToCharArray().Select(c => c.ToString()).ToList(); + + // 逐字打分 + foreach (var c in chars) + { + if (MaleWords.Contains(c)) maleScore += 10; + else if (FemaleWords.Contains(c)) femaleScore += 10; + else if (NeutralWords.Contains(c)) { } + else maleScore += 1; // 生僻字默认偏男 + } + + // 规则1:叠字 99% 女性(如婷婷、菲菲、娜娜) + if (givenName.Length >= 2 && givenName[0] == givenName[1]) + { + string c = givenName[0].ToString(); + if (FemaleRepeatWords.Contains(c) || FemaleWords.Contains(c)) + return YesNoPolicy.NO; + } + + // 规则2:强特征字优先 + bool hasStrongMale = chars.Any(MaleWords.Contains); + bool hasStrongFemale = chars.Any(FemaleWords.Contains); + + if (hasStrongMale && !hasStrongFemale) return YesNoPolicy.YES; + if (hasStrongFemale && !hasStrongMale) return YesNoPolicy.NO; + + // 规则3:分数对比 + if (maleScore > femaleScore) return YesNoPolicy.YES; + if (femaleScore > maleScore) return YesNoPolicy.NO; + + return ""; + } + + /// + /// 简化调用:直接返回 bool? (true=男,false=女,null=未知) + /// + public static bool? IsMale(string name) + { + var g = PredictGender(name); + return g == YesNoPolicy.YES ? true : g == YesNoPolicy.NO ? false : null; + } + } +}