update
This commit is contained in:
@@ -5,81 +5,18 @@ use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
use lazy_static::lazy_static;
|
||||
use jieba_rs::Jieba;
|
||||
use crate::engine::tag_dict;
|
||||
|
||||
// 全局 jieba 分词器
|
||||
lazy_static! {
|
||||
static ref JIEBA: Jieba = Jieba::new();
|
||||
}
|
||||
|
||||
// ── 常量 ──
|
||||
|
||||
/// 模特称呼前缀(移除用)
|
||||
const MODEL_TITLE_PREFIX: &[&str] = &[
|
||||
"美女模特", "性感女神", "清纯美女", "气质美女", "可爱女孩",
|
||||
"甜美少女", "时尚达人", "女神", "美女", "模特", "小姐姐",
|
||||
"小仙女", "御姐", "萝莉", "辣妹", "潮人",
|
||||
];
|
||||
|
||||
/// 停用词(标签过滤用)
|
||||
const STOP_WORDS: &[&str] = &[
|
||||
"写真", "套图", "图片", "照片", "摄影", "拍摄", "风格",
|
||||
"系列", "作品", "专辑", "主题", "人像", "艺术", "唯美",
|
||||
"诱惑", "性感", "迷人", "魅力", "时尚", "潮流", "高清",
|
||||
"原版", "无水印", "私房", "内衣", "比基尼", "泳装",
|
||||
"旗袍", "制服", "丝袜", "美腿", "美胸", "翘臀",
|
||||
"cosplay", "cos", "角色扮演", "动漫", "游戏",
|
||||
"classic", "exclusive", "premium", "collection",
|
||||
"photo", "photos", "video", "videos", "set", "vol",
|
||||
"no", "番号", "编号", "期号",
|
||||
];
|
||||
// ── 常量(仅保留正则相关常量,人物规则从 tag_dict 动态读取)──
|
||||
|
||||
/// 保留词性(仅用于 jieba 分词场景,这里做占位符)
|
||||
const _KEEP_POS: &[&str] = &["n", "nr", "ns", "nz", "v", "vn", "a", "an", "eng", "x"];
|
||||
|
||||
/// 工作室/平台排除名(大幅扩充)
|
||||
const EXCLUDED_NAMES: &[&str] = &[
|
||||
// 工作室/平台
|
||||
"Pure Media", "XiuRen秀人网", "尤蜜", "魅妍社", "花漾",
|
||||
"瑞丝馆", "撸图", "情欲萝莉", "蜜桃社", "tgod",
|
||||
"MiStar", "BLUECAKE", "HuaYang", "YouMi", "秀人",
|
||||
"XIUREN", "YouMi尤蜜", "IMiss爱蜜社", "MiiTao蜜桃社",
|
||||
"MFStar模范学院", "TGOD推女神", "DKGirl御女郎",
|
||||
"XINGYAN星颜社", "XIAOYU语画界", "YOUMI尤蜜荟",
|
||||
"FAIRY CLUB", "FAIRY-CLUB", "FEILIN嗲囡囡",
|
||||
"CANDY糖果画报", "YouWu尤物馆", "UXING优星馆",
|
||||
"108TV", "108酱", "RuiSG瑞丝馆", "SIW斯文传媒",
|
||||
"SLADY猎女神", "SSA丝社", "SJA佳爷", "TASHA系列",
|
||||
"Ugirls爱尤物", "V Girls", "VGirl", "WingS影私荟",
|
||||
"YALAYI雅拉伊", "ISHOW爱秀", "ISHOW",
|
||||
"写真", "私房", "套图", "系列", "合集", "作品",
|
||||
// 国家/地区
|
||||
"日本", "韩国", "中国", "台湾", "香港", "泰国", "越南",
|
||||
"新加坡", "马来西亚", "菲律宾", "印度尼西亚", "美国", "俄罗斯",
|
||||
"东京", "首尔", "北京", "上海", "广州", "深圳", "成都", "重庆",
|
||||
"曼谷", "胡志明", "新加坡市", "马尼拉", "雅加达",
|
||||
// 非人名常见词
|
||||
"模特", "美女", "女神", "小姐姐", "小仙女", "御姐", "萝莉",
|
||||
"性感", "诱惑", "清纯", "甜美", "可爱", "迷人", "魅力",
|
||||
"时尚", "潮流", "高清", "原版", "无水印", "完整版",
|
||||
"比基尼", "泳装", "内衣", "丝袜", "制服", "旗袍", "睡衣",
|
||||
"浴室", "浴缸", "床上", "沙发", "厨房", "阳台",
|
||||
"室内", "户外", "街拍", "旅拍", "海边", "沙滩", "泳池",
|
||||
"cosplay", "角色扮演", "动漫", "游戏",
|
||||
"超短裙", "包臀裙", "紧身裙", "连衣裙", "牛仔裤", "热裤",
|
||||
"高跟鞋", "运动鞋", "凉鞋",
|
||||
"美腿", "美胸", "翘臀", "玉足", "美背", "香肩", "蛮腰",
|
||||
"白皙", "肌肤", "纹身",
|
||||
"日系", "韩系", "森系", "复古", "冷艳", "优雅", "知性",
|
||||
"俏皮", "温柔", "妩媚", "野性", "热情", "慵懒", "迷离",
|
||||
"唯美", "清新", "冷傲",
|
||||
// 工作室前缀
|
||||
"PureMedia", "BlueCake", "HuaYang", "XiuRen", "MiStar",
|
||||
"YouMi", "MFStar", "TGOD", "DKGirl", "XINGYAN", "XIAOYU",
|
||||
"YOUMI", "FAIRYCLUB", "FEILIN", "CANDY", "YouWu", "UXING",
|
||||
"RuiSG", "SIW", "SLADY", "SSA", "SJA", "TASHA", "Ugirls",
|
||||
"VGIRLS", "VGIRL", "WING", "YALAYI", "ISHOW",
|
||||
];
|
||||
|
||||
lazy_static! {
|
||||
/// 编译后的正则
|
||||
static ref RE_COSPLAY: Regex = Regex::new(
|
||||
@@ -110,19 +47,15 @@ lazy_static! {
|
||||
r"(?i)(私房|写真|内衣|比基尼|泳装|旗袍|制服|街拍|室内|户外|浴室|浴室|浴缸|床上|床照|内衣|透视|包臀裙|紧身裙|超短裙|丝袜|美腿|美臀|美胸|翘臀|性感|清纯|甜美|气质|可爱|女神|嫩模|模特)"
|
||||
).unwrap();
|
||||
|
||||
/// 停用词集合
|
||||
static ref STOP_WORDS_SET: HashSet<&'static str> = {
|
||||
let mut s = HashSet::new();
|
||||
for w in STOP_WORDS { s.insert(*w); }
|
||||
s
|
||||
/// 停用词集合(从 tag_dict 动态获取)
|
||||
static ref STOP_WORDS_SET: HashSet<String> = {
|
||||
tag_dict::get_tag_dict().get_stop_words().clone()
|
||||
};
|
||||
|
||||
/// 排除名集合(小写)
|
||||
/// 排除名集合(从 tag_dict 动态获取)
|
||||
static ref EXCLUDED_LOWER: HashSet<String> = {
|
||||
let mut s = HashSet::new();
|
||||
for n in EXCLUDED_NAMES { s.insert(n.to_lowercase()); }
|
||||
s.extend(["youmi", "huayang", "bluecake", "mistar", "xiuren", "秀人"].map(|s| s.to_string()));
|
||||
s
|
||||
let dict = tag_dict::get_tag_dict();
|
||||
dict.get_excluded_persons().clone()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -162,8 +95,8 @@ pub fn extract_person_name_from_title(title: &str) -> Option<String> {
|
||||
// 步骤3: 移除 [工作室] 前缀
|
||||
t = RE_BRACKET_PREFIX.replace(&t, "").trim().to_string();
|
||||
|
||||
// 步骤4: 移除模特称呼前缀
|
||||
for prefix in MODEL_TITLE_PREFIX {
|
||||
// 步骤4: 移除模特称呼前缀(从词典动态获取)
|
||||
for prefix in tag_dict::get_tag_dict().get_person_prefixes() {
|
||||
if t.starts_with(prefix) {
|
||||
t = t[prefix.len()..].trim().to_string();
|
||||
break;
|
||||
@@ -218,7 +151,7 @@ pub fn extract_person_name_from_title(title: &str) -> Option<String> {
|
||||
for cap in RE_EN_NAME.captures_iter(&t) {
|
||||
let name = clean_name(&cap[1]);
|
||||
// 排除停用词
|
||||
if STOP_WORDS_SET.contains(&name.to_lowercase().as_str()) { continue; }
|
||||
if STOP_WORDS_SET.contains(&name.to_lowercase()) { continue; }
|
||||
if is_likely_person_name(&name) {
|
||||
return Some(name);
|
||||
}
|
||||
@@ -276,30 +209,46 @@ fn extract_name_from_segment(segment: &str) -> Option<String> {
|
||||
|
||||
// 策略1: 纯中文片段(2-6字)
|
||||
if is_pure_cn && seg.len() >= 2 && seg.len() <= 6 {
|
||||
return Some(clean_name(seg));
|
||||
// 标签词典中的词不应作为人名
|
||||
if !tag_dict::get_tag_dict().classify(seg).is_some()
|
||||
&& !tag_dict::get_tag_dict().classify(&seg.to_lowercase()).is_some() {
|
||||
return Some(clean_name(seg));
|
||||
}
|
||||
}
|
||||
|
||||
// 策略2: 尝试匹配纯中文人名模式
|
||||
if let Some(cap) = RE_CN_NAME.captures(seg) {
|
||||
let name = clean_name(&cap[1]);
|
||||
if name.len() >= 2 {
|
||||
return Some(name);
|
||||
// 标签词典中的词不应作为人名
|
||||
if !tag_dict::get_tag_dict().classify(&name).is_some()
|
||||
&& !tag_dict::get_tag_dict().classify(&name.to_lowercase()).is_some() {
|
||||
return Some(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 策略3: 纯英文片段(2-15字)
|
||||
if is_pure_en && seg.len() >= 2 && seg.len() <= 15 {
|
||||
// 过滤停用词
|
||||
if !STOP_WORDS_SET.contains(&seg.to_lowercase().as_str()) {
|
||||
return Some(clean_name(seg));
|
||||
if !STOP_WORDS_SET.contains(&seg.to_lowercase()) {
|
||||
// 标签词典中的词不应作为人名
|
||||
if !tag_dict::get_tag_dict().classify(seg).is_some()
|
||||
&& !tag_dict::get_tag_dict().classify(&seg.to_lowercase()).is_some() {
|
||||
return Some(clean_name(seg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 策略4: 尝试匹配纯英文人名模式
|
||||
if let Some(cap) = RE_EN_NAME.captures(seg) {
|
||||
let name = clean_name(&cap[1]);
|
||||
if name.len() >= 2 && !STOP_WORDS_SET.contains(&name.to_lowercase().as_str()) {
|
||||
return Some(name);
|
||||
if name.len() >= 2 && !STOP_WORDS_SET.contains(&name.to_lowercase()) {
|
||||
// 标签词典中的词不应作为人名
|
||||
if !tag_dict::get_tag_dict().classify(&name).is_some()
|
||||
&& !tag_dict::get_tag_dict().classify(&name.to_lowercase()).is_some() {
|
||||
return Some(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,6 +326,11 @@ pub fn is_likely_person_name(name: &str) -> bool {
|
||||
// 全部是大写字母且长度 > 6 的排除(通常是缩写/工作室名)
|
||||
if name.chars().all(|c| c.is_ascii_uppercase()) && name.len() > 6 { return false; }
|
||||
|
||||
// 标签词典中的词不应作为人名(tags 永远不出现在 person 中)
|
||||
if tag_dict::get_tag_dict().classify(name).is_some() { return false; }
|
||||
// 也检查小写版本
|
||||
if tag_dict::get_tag_dict().classify(&name.to_lowercase()).is_some() { return false; }
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
@@ -389,8 +343,8 @@ pub fn clean_person_name(raw_name: &str) -> (String, Vec<(String, String)>) {
|
||||
let mut aliases = Vec::new();
|
||||
let mut name = raw_name.to_string();
|
||||
|
||||
// 1. 移除前缀
|
||||
for prefix in MODEL_TITLE_PREFIX {
|
||||
// 1. 移除前缀(从词典动态获取)
|
||||
for prefix in tag_dict::get_tag_dict().get_person_prefixes() {
|
||||
if name.starts_with(prefix) {
|
||||
aliases.push(("prefix".to_string(), prefix.to_string()));
|
||||
name = name[prefix.len()..].trim().to_string();
|
||||
@@ -557,9 +511,8 @@ fn extract_person_from_tags(tags: &[jieba_rs::Tag]) -> Option<String> {
|
||||
if next_tag == "eng" ||
|
||||
(next_word.chars().all(|c| c.is_ascii_alphabetic()) && next_word.len() >= 2 && next_word.len() <= 15) {
|
||||
let lower = next_word.to_lowercase();
|
||||
// 跳过停用词(如 photos、cosplay 等停在停用词之前)
|
||||
let stop_words: std::collections::HashSet<&str> = STOP_WORDS.iter().copied().collect();
|
||||
if !stop_words.contains(lower.as_str()) && !EXCLUDED_LOWER.contains(&lower) {
|
||||
// 跳过停用词(从词典动态获取)
|
||||
if !tag_dict::get_tag_dict().is_stop_word(&lower) && !EXCLUDED_LOWER.contains(&lower) {
|
||||
full_name.push_str(next_word);
|
||||
j += 1;
|
||||
continue;
|
||||
@@ -599,8 +552,8 @@ fn extract_tags_from_jieba_result(
|
||||
let mut result = Vec::new();
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
// 停用词
|
||||
let stop_words: HashSet<&str> = STOP_WORDS.iter().copied().collect();
|
||||
// 停用词(从词典动态获取)
|
||||
let stop_words = tag_dict::get_tag_dict().get_stop_words().clone();
|
||||
|
||||
// 人名排除
|
||||
let person_exclude: Vec<String> = person_name
|
||||
@@ -713,7 +666,7 @@ pub fn extract_tags_from_title_legacy(title: &str, person_name: Option<&str>) ->
|
||||
if word.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
if STOP_WORDS_SET.contains(word.as_str()) {
|
||||
if STOP_WORDS_SET.contains(word) {
|
||||
continue;
|
||||
}
|
||||
if word.chars().all(|c| c.is_ascii_digit() || c == '.') {
|
||||
|
||||
Reference in New Issue
Block a user