Skip to content

Helpers

ChouChiu edited this page Sep 23, 2026 · 1 revision

辅助工具

lyrics-core 提供了一组无状态的辅助工具模块,位于 lyrics_helper::helpers 命名空间下。

类型检测 (type_helper)

get_lyrics_types

pub fn get_lyrics_types(input: &str) -> LyricsRawTypes

识别输入文本的歌词格式。

检测判定顺序:

  1. 结构化信封判定:先尝试用 serde_json 解析,依次匹配 Apple Music、Spotify、Musixmatch 与网易云完整 YRC 的 JSON 特征结构;再用 quick-xml 扫描 XML 节点,区分 TTML 与 QRC XML 信封。通过真实解析结构判定,避免正文包含关键词时发生误判。
  2. 行特征与正则判定:当非结构化数据时,按行扫描正则:[type:LyricifyLines] -> KRC -> YRC -> Lyricify Syllable -> QRC -> Lyricify Lines -> LRC。
  3. 均不匹配时返回 LyricsRawTypes::Unknown。
use lyrics_helper::helpers::type_helper::get_lyrics_types;
use lyrics_helper::LyricsRawTypes;

let content = "[00:12.00]Hello World\n[00:15.50]Second line";
assert_eq!(get_lyrics_types(content), LyricsRawTypes::Lrc);

Note

QrcFull、YrcFull、AppleJson 判定成功时表示输入为外层传输信封。parse() 对信封类型返回 None,调用方需先提取内嵌歌词文本。

格式谓词与工具函数

每种格式均提供独立的 is_* 谓词函数(签名均为 fn(&str) -> bool):

  • is_lrc、is_lyricify_syllable、is_lyricify_lines
  • is_qrc、is_qrc_full(QRC XML 信封)
  • is_krc(已解密文本)
  • is_yrc、is_yrc_full(网易云 JSON 信封)
  • is_ttml、is_apple_json、is_spotify、is_musixmatch

类型转换与显示名:

  • try_parse_raw_type(name: &str) -> Option<LyricsRawTypes>:按格式名称解析(如 "qrc (xml)" -> QrcFull)。
  • get_raw_type_display_name(raw_type: LyricsRawTypes) -> String:获取可读格式名称。
  • is_lyrics_type(lyrics: &str, t: LyricsTypes) -> bool:输入是否匹配指定歌词类型。
  • is_lyrics_type_any(lyrics: &str, types: &[LyricsTypes]) -> bool:输入是否属于给定类型列表之一。

时间偏移 (offset_helper)

add_offset

pub fn add_offset(lines: &mut [LineInfo], offset: i32)

对歌词行批量应用时间偏移(毫秒)。内部实现为 time -= offset:

  • 正值:时间戳前移,歌词更早出现(与标准 LRC [offset:] 语义一致)。
  • 负值:时间戳后移,歌词延后出现。
  • 递归平移所有 sub_line(背景和声)。
  • 音节行的行时间(start_time / end_time)与各音节自身的起止时间一同平移。
use lyrics_helper::helpers::offset_helper::add_offset;
use lyrics_helper::{parse, LyricsRawTypes};

let mut data = parse("[00:12.00]Hello World", LyricsRawTypes::Lrc).unwrap();
if let Some(lines) = &mut data.lines {
    // 前移 500ms:12.000s 变为 11.500s
    add_offset(lines, 500);
}

音节列表时间偏移

  • add_offset_to_syllables(syllables: &mut [SyllableInfo], offset: i32):平移普通音节切片。
  • add_offset_to_syllable_items(syllables: &mut [SyllableItem], offset: i32):平移抽象音节项,内部合并的 FullSyllableInfo 各子音节同步调整。

中文处理 (chinese_helper)

基于 ferrous-opencc 实现的快速繁简文本转换。转换器内部懒加载并全局复用。

to_simplified / to_traditional

use lyrics_helper::helpers::chinese_helper::{to_simplified, to_traditional};

assert_eq!(to_simplified("漢語歌詞"), "汉语歌词");
assert_eq!(to_traditional("汉语歌词"), "漢語歌詞");

to_simplified_forced

pub fn to_simplified_forced(text: &str) -> String

增强型简繁转换:在 OpenCC 转换基础上,针对词典未覆盖的「藉/咀/昇/髒」等异体字做强制替换,对应上游 ChineseHelper.ToSC(text, force: true),用于曲目匹配打分。

字符串工具 (string_helper)

字符判定与时间格式化

  • format_time_ms_to_timestamp_string(time_ms: f32) -> String:将毫秒转为 mm:ss.SSS 格式字符串。负值作为 0 处理。
  • remove_front_back_brackets(s: &str) -> String:剥离首尾成对的圆括号(半角 () 或全角 ())。
  • is_number(s: &str) -> bool:判断字符串是否非空且纯由 ASCII 数字组成。

中日文字符集区别

use lyrics_helper::helpers::string_helper::{has_chinese, is_chinese_or_japanese_character};

// has_chinese 严格限定在 CJK 统一表意文字区 (\u{4E00}-\u{9FFF})
assert!(has_chinese("中文"));
assert!(!has_chinese("ひらがな"));

// is_chinese_or_japanese_character 覆盖平假名、片假名、扩展 A 等,用于音节词边界判定
assert!(is_chinese_or_japanese_character('ひ'));

空白处理与相似度计算

  • collapse_whitespace(s: &str) -> String:将连续空白(空格、Tab、换行等)折叠为单个半角空格,并去除首尾空白。
  • remove_duo_spaces(s: &str) -> String:仅将连续的两个以上半角空格压缩为单个空格,不执行首尾 trim。
  • compute_text_same(s1: &str, s2: &str, case_sensitive: bool) -> f64:基于最长公共子序列(LCS)计算相似度分值(0.0 ~ 100.0)。
  • lcs_length(s1: &str, s2: &str) -> usize:计算两字符串的最长公共子序列长度。

数学工具 (math_helper)

处理带 None 的时间戳极值计算:

  • min_opt(a: Option<i32>, b: Option<i32>) -> Option<i32>:返回两者中的较小值;均为 None 时返回 None。
  • max_opt(a: Option<i32>, b: Option<i32>) -> Option<i32>:返回两者中的较大值;均为 None 时返回 None。

书写约定提取 (conventions)

部分平台歌词(如 LRC/QRC/YRC)未定义结构化声部或伴唱字段,转录者常将歌手标签写在行首(如 周杰伦:)或用括号包裹伴唱(如 (yeah))。本模块提供从文本约定中还原结构化信息的能力:

函数 功能说明
apply_speaker_labels(lines, artists) 根据曲目歌手列表识别行首歌手名或独立标签行,将对齐属性分别置为 Left(主唱)与 Right(合唱/副部);独立标签行将被清除
split_background_vocals(lines) 将逐字行内的括号内容拆分为该行的背景和声子行(sub_line)
unwrap_brackets(text) 去除短语外层的包围括号
fold_bracketed_echoes(lines) 将整行均为括号的伴唱行并入前一行的 sub_line
merge_continued_lines(lines) 根据行尾逗号或标点,将跨行书写的断句合并回主行

Important

上述函数会对歌词行结构进行破坏性修改(重排音节、合并/删除行),解析器不会自动调用。建议的组合调用顺序: apply_speaker_labels -> split_background_vocals -> 关联翻译 -> fold_bracketed_echoes -> merge_continued_lines。

use lyrics_helper::helpers::conventions::{apply_speaker_labels, split_background_vocals};
use lyrics_helper::{LineInfo, LyricsAlignment, SyllableInfo};

let mut lines = vec![
    LineInfo::new_line("周杰伦:".to_string(), Some(0), None),
    LineInfo::new_syllable_with_time(
        vec![
            SyllableInfo::new("Hold ".to_string(), 1000, 1500).into(),
            SyllableInfo::new("on (yeah)".to_string(), 1500, 2500).into(),
        ],
        Some(1000),
        None,
    ),
];

apply_speaker_labels(&mut lines, &["周杰伦".to_string()]);
split_background_vocals(&mut lines);

assert_eq!(lines.len(), 1); // 标签行已被移除
assert_eq!(lines[0].text_from_any(), "Hold on");
assert_eq!(lines[0].alignment(), LyricsAlignment::Left);
assert_eq!(lines[0].sub_line().unwrap().text_from_any(), "yeah");

跨文档逐词计时 (word_timing)

pub fn apply_word_timings(lines: &mut [LineInfo], word_lines: &[LineInfo])

网易云 YRC 等逐词歌词通常存在转录缺失标点、分词吞空格等问题。apply_word_timings 将逐词文档(word_lines)的音节时间精确迁移至排版规范的逐行文本(lines)上:

  • 行对齐:两份文档按行开始时间匹配(误差允许 ±1000ms),每行仅配对一次。
  • 分词对齐:逐词文档的词吃掉文本中截至该词末尾字符的文本,空白与标点并入前一个词,使得音节拼接后与原排版文本完全一致。
  • 对齐失败保护:整行文本无法匹配时,放弃该行逐词拆分并保留原行级状态,绝不产生部分贴合的半残行。
  • 成功后行变体升级为 Syllable / FullSyllable,原有分边、子行与翻译完整保留。
use lyrics_helper::helpers::word_timing::apply_word_timings;
use lyrics_helper::{LineInfo, SyllableInfo};

let mut rows = vec![LineInfo::new_line(
    "Ugh, you're a monster".to_string(),
    Some(90),
    Some(2160),
)];

let words = vec![LineInfo::new_syllable(vec![
    SyllableInfo::new("Ugh".to_string(), 90, 420).into(),
    SyllableInfo::new("you're ".to_string(), 420, 690).into(),
    SyllableInfo::new("a ".to_string(), 690, 960).into(),
    SyllableInfo::new("monster".to_string(), 960, 2160).into(),
])];

apply_word_timings(&mut rows, &words);

let syllables = rows[0].syllables().expect("对齐成功转为音节行");
assert_eq!(syllables[0].text(), "Ugh, "); // 标点与空格自动附着
assert_eq!(rows[0].text_from_any(), "Ugh, you're a monster");

歌词优化模块 (optimization)

对应上游 Helpers/Optimization 中的各项规整算法:

子模块 核心函数 功能
syllable_word_merger merge(line) 合并连续音节为同一单词(FullSyllableInfo),保留各自时间
apple_music prepare_lyrics(lines) / capitalization_normalization(lines) 音节修剪、同词合并、翻译冗余清洗及大小写规整
info_lines check_info_lines(lines, track_info) / is_info_line 识别作词/作曲/编曲等信息署名行及版权声明
musixmatch standardize_musixmatch_lyrics(lines) 合并独立空白片段并规整 richsync 时间
sync_downgrade downgrade_to_line_synced(lines) 逐字音节降级为纯逐行歌词
yrc standardize_yrc_lyrics(lines) 移除末尾无意义空音节,清理网易云 YRC 冗余标记
explicit clean(str, strong) / fix_explicit(str) 脏词过滤及星号形式原词还原

Clone this wiki locally