Skip to content

SQLite 歌曲入库命名规则

本文用于约束本地媒体文件扫描入库时,如何从文件名中解析歌手、歌名和关键词,并写入 SQLite 索引。

当前文档面向 KTV 点歌场景,重点覆盖 Android 目录扫描入库链路;后续如果 macOS 或桌面端也接入 SQLite,应复用同一套规则。

1. 目标

  • 将文件名解析为稳定、可查询的结构化字段。
  • 保证合唱歌曲、多关键词歌曲可以被正确索引。
  • 对无法完全识别的文件名给出统一降级策略,避免直接漏歌。

2. 标准命名格式

标准文件名格式定义为:

text
歌手字段-歌曲名-关键词1-关键词2-关键词3....扩展名

示例:

text
周杰伦&费玉清-千里之外-国语-流行音乐.mp4

- 做一级拆分后,应得到:

  1. 第 1 段:歌手字段
  2. 第 2 段:歌曲名
  3. 第 3 段及以后:关键词字段

对应上面的示例:

  • 歌手字段:周杰伦&费玉清
  • 歌曲名:千里之外
  • 关键词:国语流行音乐

说明:

  • 示例中的 & 出现在歌手字段中,表示多歌手合唱,不属于歌曲名的一部分。
  • 歌曲名字段整体保留,不再按 & 或其他符号二次拆分。

3. 文件名预处理规则

正式解析前,先做以下预处理:

  1. 去掉文件扩展名,只保留基础文件名。
  2. 对整串文件名做首尾空白裁剪。
  3. - 拆分为多个片段。
  4. 对每个片段做 trim(),去掉片段首尾空格。
  5. 过滤掉关键词区中的空片段。

建议兼容但不改变标准规则的处理:

  • 历史文件如果使用了 -,可以先归一化为 - 再解析。
  • 标准入库命名仍以半角连字符 - 为一级分隔符。

3.1 连字符白名单

由于半角连字符 - 同时承担一级分隔符角色,歌手名或歌名中如果合法包含 -,默认规则无法自动区分,必须使用白名单兜底。

白名单数据文件:

  • docs/sqlite_hyphen_whitelist.yaml

当前建议只维护一类白名单:

  • artist_names:合法包含 - 的歌手名

当前提供的初始化数据只作为第一版种子,后续发现新样本时继续追加,不要在代码里写死。

推荐解析顺序:

  1. 文件名归一化后,先按 - 拆分片段。
  2. 歌手字段优先尝试白名单精确匹配,建议采用“最长命中优先”。
  3. 歌手字段确定后,从右到左消费 languagetags 关键词片段。
  4. 左侧歌手片段与右侧关键词片段之间的中间剩余片段,重新拼接为歌曲名。
  5. 歌名统一由“左侧歌手片段与右侧关键词片段消费完成后的中间剩余片段”重组生成。
  6. 如果歌手白名单没有命中,再回退到默认规则。

3.2 连字符白名单匹配流程

为避免不同实现对“最长命中优先”的理解不一致,白名单匹配流程固定如下。

假设文件名去掉扩展名并归一化后得到:

text
片段1-片段2-片段3-片段4-...

则解析时必须按以下顺序执行:

  1. 先尝试匹配歌手白名单。
  2. 歌手白名单匹配完成后,消费掉对应片段。
  3. 再从剩余片段的右侧开始识别并消费 languagetags
  4. 左右两侧消费完成后,中间剩余片段重新拼接为歌曲名。
  5. 左右两侧消费完成后,中间剩余片段直接重组为歌名。

3.2.1 歌手白名单匹配规则

以全部片段的起始位置为基准,尝试从长到短拼接:

  • 片段1-片段2-片段3
  • 片段1-片段2
  • 片段1

如果某个拼接结果命中 artist_names,则立即采用该结果作为歌手字段,并停止歌手白名单匹配。

这就是“最长命中优先”的具体含义:

  • 优先匹配能覆盖更多片段的候选
  • 一旦命中更长候选,不再继续尝试更短候选

如果歌手白名单完全未命中,则回退到默认规则:

  • 第 1 段作为歌手字段

3.2.2 歌名生成规则

在当前规则下,不再维护歌名白名单。

歌名的确定方式固定为:

  1. 先完成歌手白名单匹配。
  2. 再完成右侧 language / tags 片段消费。
  3. 将左右两侧消费后的中间剩余片段按 - 重新拼接为歌名。

也就是说,歌名中的合法连字符 - 会自然保留在中间剩余片段中,不再需要额外的歌名白名单参与切分。

3.2.3 关键词消费规则

关键词消费改为“右侧优先”:

  • 所有已被歌手字段消费的片段,不再参与后续解析
  • 只允许从当前剩余片段的最右侧开始识别并消费 language / tags
  • 一旦某个右侧片段既未命中 language、也未命中 tags,则停止继续向左消费
  • 左右两侧消费完成后,中间剩余片段全部归入歌曲名

也就是说,关键词区不是固定的“原始第 3 段以后”,而是“歌手字段从左消费后、再由右侧连续命中的 language / tags 片段组成的尾部区间”。

3.2.4 示例 1

文件名:

text
A-Lin-给我一个理由忘记-国语-流行

拆分片段:

  • A
  • Lin
  • 给我一个理由忘记
  • 国语
  • 流行

歌手匹配时:

  • 先尝试 A-Lin-给我一个理由忘记
  • 再尝试 A-Lin
  • 命中 artist_names

因此:

  • 歌手消费片段 1-2
  • 从右侧识别出关键词 国语流行
  • 中间剩余片段重组后,歌曲名为 给我一个理由忘记

3.2.5 示例 2

文件名:

text
A-Lin-Love-Love-Love-国语-流行

假设:

  • A-Linartist_names

拆分片段:

  • A
  • Lin
  • Love
  • Love
  • Love
  • 国语
  • 流行

解析过程:

  1. 歌手白名单命中 A-Lin,消费片段 1-2
  2. 从右侧消费关键词,识别出 国语流行
  3. 中间剩余片段为 LoveLoveLove
  4. 中间剩余片段按 - 重组为 Love-Love-Love

最终结果:

  • 歌手:A-Lin
  • 歌曲名:Love-Love-Love
  • 关键词:国语流行

3.2.6 冲突处理规则

如果同一位置存在多个白名单候选:

  • 优先选择消费片段更多的候选
  • 如果消费片段数相同,则优先选择白名单文件中靠前的条目

如果歌手白名单命中后,导致剩余片段不足以组成有效歌曲名:

  • 该次白名单命中视为无效
  • 回退到更短候选继续尝试
  • 如果所有候选都失败,则回退到默认规则

3.2.7 默认规则的边界

默认规则只在歌手白名单完全未命中时生效:

  • 歌手白名单未命中时,歌手 = 原始第 1 段
  • 歌名 = 左侧歌手片段与右侧关键词片段消费完成后的中间剩余片段,按 - 重新拼接

不能在歌手白名单已经命中的情况下,再把原始第 2 段固定当作歌名;也不能在右侧关键词已经可识别时,提前把这些片段错误吞进歌名。

示例:

text
A-Lin-给我一个理由忘记-国语-流行

如果 A-Linartist_names 中,则应解析为:

  • 歌手:A-Lin
  • 歌曲名:给我一个理由忘记
  • 关键词:国语流行

否则会被错误拆成:

  • 歌手:A
  • 歌曲名:Lin

因此,白名单是当前规则下的必要前置数据,而不是可选优化。

4. 字段解析规则

在引入连字符白名单和关键词识别后,字段定位不再是“固定第 1 段 / 第 2 段 / 第 3 段以后”的简单规则,而是按“左侧消费歌手、右侧消费关键词、中间归并歌名”的方式执行。

固定解析顺序如下:

  1. 去掉扩展名并完成文件名归一化。
  2. - 拆分为片段。
  3. 从左到右优先匹配歌手白名单,采用“最长命中优先”。
  4. 歌手确定后,从右到左处理关键词片段。
  5. 关键词匹配前,先对尾部片段执行“尾部噪声后缀清理”。
  6. 从右到左逐个处理尾部片段;对每个片段先尝试识别 language,未命中再尝试识别 tags,只消费尾部连续命中的片段。
  7. 左侧歌手已消费片段与右侧关键词已消费片段之间的所有剩余片段,重新用 - 拼接为歌曲名。

只有在未启用白名单和关键词识别的最简实现里,才可以近似理解为“第 1 段是歌手、第 2 段是歌名、第 3 段以后是关键词”。正式实现必须以“消费片段后的剩余区间”作为字段边界。

4.1 歌手字段

歌手字段始终从片段左侧开始确定。

如果歌手白名单命中,则歌手字段可能消费多个连续片段;如果歌手白名单未命中,则回退为原始第 1 段。

示例:

text
周杰伦&费玉清

歌手字段的处理规则:

  1. 如果包含 &,表示多歌手合唱。
  2. & 拆分歌手列表。
  3. 每个歌手名再次做 trim()
  4. 过滤空字符串后,保留原始顺序。

示例结果:

  • artistDisplayName = "周杰伦&费玉清"
  • artistNames = ["周杰伦", "费玉清"]
  • artistCount = 2
  • isChorus = true

补充约束:

  • 当前阶段只把 & 视为歌手字段中的合唱分隔符。
  • 如果歌曲名中本身包含 &,不做歌手拆分,只按歌曲名原文保留。
  • 如果歌手字段为空,则视为解析失败,进入异常降级流程。

4.2 歌曲名字段

歌曲名字段不是固定的“第 2 段”,而是“左侧歌手片段消费完成后、右侧关键词片段消费完成后,中间剩余的全部片段”。

示例结果:

  • title = "千里之外"

处理规则:

  1. 将中间剩余片段按原顺序保留。
  2. - 将中间剩余片段重新拼接为歌曲名。
  3. 对最终歌曲名做首尾空白裁剪。
  4. 不再按 &/+ 等符号继续拆分。

如果歌曲名字段为空,则视为解析失败,进入异常降级流程。

示例:

text
A-Lin-Love-Love-国语-流行_副本(1).mp4

解析过程:

  • 歌手白名单命中 A-Lin
  • 右侧关键词识别得到 国语流行
  • 中间剩余片段为 LoveLove

最终歌曲名应为:

  • Love-Love

4.3 关键词字段

关键词字段也不是固定的“第 3 段及以后”,而是“在歌手片段已经从左侧消费完成后,从右侧开始尝试识别并消费的片段”。

示例:

  • 国语
  • 流行音乐

处理规则:

  1. 只允许从当前剩余片段的最右侧开始识别。
  2. 识别前先执行“尾部噪声后缀清理”。
  3. 命中的关键词按原始出现顺序写入结构化字段。
  4. 去除空字符串。
  5. 对完全重复的关键词可去重。
  6. 逐个与关键词词典匹配。

5. 关键词匹配规则

关键词不直接当作自由文本使用,而是优先映射到结构化字段。

对文件名按 - 拆分后,关键词识别的前提是:

  • 左侧歌手字段已经先完成消费
  • 语言和标签只允许从当前剩余片段的最右侧开始识别
  • 只有右侧连续命中的关键词片段才会被消费

也就是说,语言和标签等信息只能从“歌手已消费完成后,剩余片段的右侧尾部”里识别,不能扫描整个中间区域,更不能反向吞掉真实歌名。

为兼容当前命名,本文继续使用字段名 language,但其语义调整为“多值语言列表”;如果后续新建表结构,也可以命名为 languages

语种 language 为多值字段;歌曲类型、业务标签、版本标签统一归入 tags 多值字段。

5.0 关键词标准化规则

关键词在进入词典匹配前,必须先做统一标准化。

标准化的目的只有一个:

  • 提高词典命中率

标准化不会改变原始入库值的保存方式:

  • 标准化结果只用于匹配过程和中间计算,不直接替换原文
  • 当前方案不要求将原始关键词片段正式落库

推荐标准化顺序如下:

  1. 首尾空白裁剪:对关键词执行 trim()
  2. 空白压缩:将连续空白折叠为单个空格
  3. 全角半角归一化:如 DJ -> DJLive -> Live
  4. 大小写归一化:英文统一转小写后参与匹配
  5. 繁简体归一化:如 國語 -> 国语閩南話 -> 闽南话
  6. 分隔符归一化:将无语义差异的符号归一,如 -> -
  7. 标点裁剪:去掉关键词首尾无意义标点,但不破坏关键词正文

推荐示例:

原始关键词标准化后
國語国语
閩南話闽南话
DJdj
Livelive
流行音樂流行音乐

补充约束:

  • 标准化必须在语言匹配和标签匹配前统一执行
  • 词典匹配应基于标准化后的值,而不是原始值
  • 词典命中后写入的是“标准分类名”或“标准标签名”,不是标准化后的中间字符串
  • 如果标准化后为空字符串,则该关键词视为无效关键词,不参与匹配

当前阶段建议最低支持以下三类归一化能力:

  • 繁简体归一
  • 全角半角归一
  • 英文大小写归一

5.0.1 尾部噪声后缀清理

在关键词标准化之前,还需要先处理“文件复制、导出、系统追加序号”这类无业务语义的尾部噪声。

该规则仅允许作用在“当前最右侧待识别片段”上,不能扫描或修改歌名正文。

推荐清理目标包括但不限于:

  • _副本
  • _副本(n)
  • 副本
  • 副本(n)
  • (1)(2) 这类纯序号后缀
  • _copy
  • copy

处理规则:

  1. 仅对右侧待匹配关键词片段执行。
  2. 从片段尾部开始移除无意义后缀。
  3. 清理完成后再执行关键词标准化与词典匹配。
  4. 如果清理后片段为空,则该片段视为无效关键词。
  5. 不得删除正文中的真实语义内容,例如 Live演唱会版DJ版伴奏版

示例:

原始片段清理后
流行_副本(1)流行
国语(2)国语
Live_副本Live

5.1 语言匹配规则

语言字段 language 的识别优先级最高,但该优先级作用于“单个尾部片段的分类顺序”,不是要求必须先把所有右侧片段都尝试成语言。

language 的落库值应为多值列表:

  • 单个语种示例:["国语"]
  • 多个语种示例:["国语", "粤语"]
  • 未命中任何语种时:["其它"]

如果同一首歌识别出多个语言关键词:

  • 允许全部保留
  • 去重
  • 按文件名中的原始出现顺序保存,而不是按扫描顺序倒序保存

推荐标准分类名如下:

  • 国语
  • 粤语
  • 闽南语
  • 英语
  • 日语
  • 韩语
  • 客语
  • 其它

推荐关键词映射表如下:

命中关键词标准分类名
国语普通话华语国语
粤语广东话白话粤语
闽南语闽南话台语福建话闽南语
英语英文英语
日语日文日语
韩语韩文韩语
客语客家话客语

处理规则:

  1. 从当前剩余片段的最右侧开始,逐个向左处理尾部片段。
  2. 对每个待匹配片段先执行“尾部噪声后缀清理”。
  3. 再执行“关键词标准化规则”。
  4. 先按语言词典做“完整词匹配”。
  5. 如果命中语言,则将对应标准分类名加入 language 列表,并消费该片段。
  6. 如果当前片段未命中语言,不代表停止;还应继续尝试将该片段按 tags 分类。
  7. 如果多个连续尾部片段同时命中不同语言,则全部写入 language 列表,去重后按原始出现顺序保存。
  8. 如果所有尾部连续关键词片段都没有命中语言,则 language = ["其它"]

示例:

text
周杰伦-青花瓷-国语-流行音乐

歌手字段消费后,右侧尾部待识别片段为:

  • 国语
  • 流行音乐

其中:

  • 国语 命中语言词典,写入 language = ["国语"]
  • 流行音乐 继续参与后续的标签匹配

再例如:

text
陈小云-爱情恰恰-闽南话-经典

歌手字段消费后,右侧尾部待识别片段为:

  • 闽南话
  • 经典

其中:

  • 闽南话 归一到 language = ["闽南语"]

5.2 标签匹配规则

在右侧尾部片段分类过程中,tags 识别与语言识别共享同一轮从右到左的扫描;只是对每个片段都必须先尝试语言,再尝试 tags

tags 是多值字段,用于统一承载:

  • 歌曲类型:如 流行经典摇滚
  • 业务标签:如 对唱合唱现场版
  • 版本标签:如 LiveMV伴奏版

和语种规则一致,tags 也只从“歌手已消费完成后,剩余片段的右侧尾部”中识别。

推荐标准标签名如下:

  • 流行
  • 经典
  • 摇滚
  • 民谣
  • 舞曲
  • DJ
  • 情歌
  • 儿歌
  • 戏曲
  • 对唱
  • 合唱
  • 现场版
  • Live
  • 演唱会
  • MV
  • 伴奏版
  • 原版
  • 重制版

推荐关键词映射表如下:

命中关键词标准标签名
流行流行音乐流行歌曲流行
经典经典老歌怀旧经典
摇滚摇滚乐摇滚
民谣校园民谣民谣
舞曲劲爆嗨歌舞曲
DJ电音DJ
情歌抒情情歌
儿歌童谣儿歌
戏曲黄梅戏京剧越剧戏曲
对唱对唱
合唱合唱
现场版现场版
LiveLive
演唱会演唱会
MVMV
伴奏版伴奏版
原版原版
重制版重制版

处理规则:

  1. 只处理当前剩余片段右侧尾部的连续候选片段。
  2. 对每个候选片段先执行“尾部噪声后缀清理”,再执行“关键词标准化规则”。
  3. 对单个片段先尝试匹配语言;只有未命中语言时,才继续尝试匹配 tags
  4. tags 使用“完整词匹配”优先。
  5. 每命中一个 tags 候选,就消费一个右侧片段,并继续向左尝试。
  6. 一旦某个右侧片段既未命中语言、也未命中 tags,就停止继续向左扫描,剩余中间片段保留给歌名。
  7. 所有命中的标准标签都写入 tags
  8. tags 允许多值,同时需要去重并保留首次命中顺序。
  9. 如果没有任何标签关键词命中,则 tags = []

推荐伪流程如下:

text
for 右侧尾部片段 from right to left:
  清理尾部噪声
  标准化
  if 命中 language:
    将 language 追加到语言列表
    消费该片段
    continue
  if 命中 tags:
    写入 tags
    消费该片段
    continue
  break

示例:

text
张学友-吻别-国语-流行音乐

歌手字段消费后,右侧尾部待识别片段为:

  • 国语
  • 流行音乐

其中:

  • 国语 命中语言词典,写入 language = ["国语"]
  • 流行音乐 命中标签词典,写入 tags = ["流行"]

再例如:

text
凤凰传奇-自由飞翔-国语-流行-对唱

歌手字段消费后,右侧尾部待识别片段为:

  • 国语
  • 流行
  • 对唱

其中:

  • 国语 命中语言词典
  • 流行 命中标签词典
  • 对唱 命中标签词典

最终结果:

  • language = ["国语"]
  • tags = ["流行", "对唱"]

关于 对唱/合唱 的特殊约束:

  • isChorus 只根据歌手字段是否包含 & 判定
  • 对唱合唱 只写入 tags
  • 如果出现 对唱/合唱 标签,但歌手字段没有 &,允许保留标签,同时记录解析告警

6. 推荐落库结果

以文件名:

text
周杰伦&费玉清-千里之外-国语-流行音乐.mp4

为例,推荐的解析结果如下:

字段
fileName周杰伦&费玉清-千里之外-国语-流行音乐.mp4
artistDisplayName周杰伦&费玉清
artistNames["周杰伦", "费玉清"]
artistCount2
isChorustrue
title千里之外
language["国语"]
tags["流行"]

如果文件名为:

text
陈雷-欢喜就好-闽南话-经典.mp4

则推荐结果为:

字段
artistDisplayName陈雷
artistNames["陈雷"]
title欢喜就好
language["闽南语"]
tags["经典"]

如果当前 SQLite 表结构暂时还没有拆分歌手关系表,至少应保证下面这些主表字段可被稳定保存:

  • title
  • artist
  • search_index
  • file_name
  • media_path

如果后续要支持歌手维度检索、合唱检索、歌手聚合,歌手信息也推荐使用关联表,而不是只依赖主表中的展示字段。

建议补充以下字段或关联表:

  • artist_display_name
  • artist_count
  • is_chorus
  • song_artists 关联表

6.1 关联表推荐落库方式

由于 languagetags 都是多值字段,且后续需要支持“按语言获取列表”“按语言过滤后继续按歌名搜索”等查询,SQLite 落库方案在本文中明确固定为关联表实现,不再推荐也不再讨论 JSON 字符串、逗号分隔字符串或其他非结构化单列文本方案。

推荐使用关联表:

  • song_artists(song_id, artist_name)
  • song_languages(song_id, language)
  • song_tags(song_id, tag)

推荐原因:

  • 可以高效按单个歌手筛选歌曲
  • 可以支持合唱歌曲按任一歌手命中
  • 可以支持歌手聚合、歌手维度统计
  • 可以高效按语言筛选歌曲
  • 可以在语言过滤后继续按歌名排序或前缀搜索
  • 可以自然支持一首歌多个语言、多个标签
  • 可以避免 LIKE '%国语%' 这类无法稳定命中索引的查询方式

推荐最小表结构如下:

sql
CREATE TABLE songs (
  id INTEGER PRIMARY KEY,
  title TEXT NOT NULL,
  title_norm TEXT NOT NULL,
  artist_display_name TEXT NOT NULL,
  file_name TEXT NOT NULL,
  media_path TEXT NOT NULL UNIQUE
);

CREATE TABLE song_artists (
  song_id INTEGER NOT NULL,
  artist_name TEXT NOT NULL,
  PRIMARY KEY (song_id, artist_name)
);

CREATE TABLE song_languages (
  song_id INTEGER NOT NULL,
  language TEXT NOT NULL,
  PRIMARY KEY (song_id, language)
);

CREATE TABLE song_tags (
  song_id INTEGER NOT NULL,
  tag TEXT NOT NULL,
  PRIMARY KEY (song_id, tag)
);

CREATE INDEX idx_song_languages_language_song
ON song_languages(language, song_id);

CREATE INDEX idx_song_tags_tag_song
ON song_tags(tag, song_id);

CREATE INDEX idx_song_artists_artist_song
ON song_artists(artist_name, song_id);

CREATE INDEX idx_songs_title_norm
ON songs(title_norm);

补充说明:

  • artist_display_name 保留原始展示值,例如 周杰伦&费玉清
  • song_artists.artist_name 保存拆分后的单个歌手名,例如 周杰伦费玉清
  • PRIMARY KEY (song_id, artist_name)PRIMARY KEY (song_id, language)PRIMARY KEY (song_id, tag) 可直接防止重复值
  • 如果未来需要级联删除,可在正式建表时补上外键约束

推荐查询示例:

按单个歌手获取歌曲列表:

sql
SELECT s.*
FROM song_artists sa
JOIN songs s ON s.id = sa.song_id
WHERE sa.artist_name = ?
ORDER BY s.title_norm ASC;

按歌手过滤后继续按歌名搜索:

sql
SELECT s.*
FROM song_artists sa
JOIN songs s ON s.id = sa.song_id
WHERE sa.artist_name = ?
  AND s.title_norm LIKE ? || '%'
ORDER BY s.title_norm ASC;

按单个语言获取歌曲列表:

sql
SELECT s.*
FROM song_languages sl
JOIN songs s ON s.id = sl.song_id
WHERE sl.language = ?
ORDER BY s.title_norm ASC;

按语言过滤后继续按歌名搜索:

sql
SELECT s.*
FROM song_languages sl
JOIN songs s ON s.id = sl.song_id
WHERE sl.language = ?
  AND s.title_norm LIKE ? || '%'
ORDER BY s.title_norm ASC;

查询同时属于多个语言的歌曲:

sql
SELECT s.*
FROM songs s
JOIN song_languages sl ON sl.song_id = s.id
WHERE sl.language IN (?, ?)
GROUP BY s.id
HAVING COUNT(DISTINCT sl.language) = 2
ORDER BY s.title_norm ASC;

因此,本文档后续提到的歌手、语言、标签字段,统一理解为:

  • artist 逻辑层:展示值 + 单歌手列表
  • language 逻辑层:多值列表
  • tags 逻辑层:多值列表
  • SQLite 落库层:关联表
  • 查询层:通过 JOIN 而不是字符串模糊匹配实现

补充约束:

  • songs 主表保留 artist_display_name 作为展示字段
  • songs 主表不要求额外保留 languagetags 冗余列
  • 单歌手检索的 SQLite 唯一权威来源是 song_artists
  • language 的 SQLite 唯一权威来源是 song_languages
  • tags 的 SQLite 唯一权威来源是 song_tags

7. 搜索索引建议

搜索索引建议由以下内容拼接组成:

  • 歌曲名
  • 歌手展示名
  • 每个独立歌手名
  • 原始文件名
  • 扩展名
  • 语种
  • 已识别关键词
  • 标题拼音/首字母
  • 歌手拼音/首字母

这样可以覆盖以下检索场景:

  • 搜歌曲名
  • 搜单个歌手名
  • 搜合唱歌手中的任一歌手
  • 搜语种
  • 搜标签关键词
  • 搜拼音首字母

8. 异常与降级规则

为了避免因为文件名不规范导致歌曲完全丢失,入库时需要定义统一的降级策略。

8.1 无法得到有效歌名

如果按 - 拆分并完成歌手、关键词消费后,无法得到有效歌名:

  • 不满足标准格式
  • 仍允许入库
  • title = 去除文件后缀后的完整文件名
  • artist = "未识别歌手"
  • parseStatus = invalid 或等价状态字段

8.2 歌手或歌曲名为空

如果歌手字段为空,或者中间剩余片段在重新拼接后为空:

  • 视为非标准命名
  • 走与上面相同的降级逻辑

8.3 关键词未命中词典

如果关键词没有命中任何词典:

  • 该关键词不参与 languagetags 落库
  • 该关键词可拼入 search_index
  • 如需排查解析行为,建议仅在开发期日志中输出,不作为正式 SQLite 字段保存

8.4 多个语言关键词同时命中

例如:

text
歌手A-歌曲B-国语-粤语

建议处理方式:

  • language 支持多值保存
  • 所有命中的语言关键词都写入 language
  • 去重后按文件名中的原始出现顺序保存

例如:

  • language = ["国语", "粤语"]

8.5 多个标签关键词同时命中

例如:

text
歌手A-歌曲B-国语-流行-经典

建议处理方式:

  • language = ["国语"]
  • tags = ["流行", "经典"]

多个标签可以并行保存,不需要再压缩成单值字段。

8.5.1 歌曲类型标签与业务标签同时命中

例如:

text
歌手A-歌曲B-国语-流行-对唱

建议处理方式:

  • language = ["国语"]
  • tags = ["流行", "对唱"]

两类标签并行保存,互不覆盖。

8.6 多歌手拆分后为空

例如出现:

text
&&-千里之外-国语

则说明歌手字段无有效值,应按异常规则处理。

8.7 解析规则版本与重建

由于语种词典、标签词典和白名单都可能持续演进,建议为每条入库记录保存解析规则版本,例如 parseRuleVersion

规则变更场景包括但不限于:

  • 新增或修改语种关键词映射
  • 新增或修改标签关键词映射
  • 新增或修改连字符白名单

发生上述变更后,建议触发对应目录的全量重扫或全量重建索引,避免新旧规则混用。

9. 推荐实现顺序

建议按下面顺序落地代码:

  1. 统一文件名预处理与一级分段。
  2. 先稳定解析 artist/title
  3. 增加 & 合唱歌手拆分。
  4. 建立连字符白名单,先兜底歌手名和歌名中的合法 -
  5. 建立关键词词典,将关键词映射到 languagetags 等字段。
  6. 未识别关键词不落库,仅在开发期日志输出,便于排查解析行为。
  7. 最后再扩展歌手关联表或更细的分类字段。

10. 与当前实现的差异

当前 Android 示例中的 SQLite 入库实现位于:

  • android/app/src/main/kotlin/com/ktv/player/ktv2_example/MainActivity.kt

当前实现的特点是:

  • 只从文件名中解析 artisttitle
  • 只按第一个分隔符拆分
  • 尚未处理 & 多歌手
  • 尚未处理歌手名或歌名中合法包含 - 的白名单场景
  • 尚未支持“左侧消费歌手、右侧消费关键词、中间重组歌名”的正式规则
  • 尚未处理关键词尾部噪声后缀,例如 _副本(1)
  • language 统一写死为 其它,尚未根据右侧尾部关键词识别
  • 尚未根据右侧尾部关键词识别 tags
  • 尚未保存结构化关键词解析结果到关联表

因此,本文档描述的是下一步应实现的目标规则,而不是当前代码已经完整具备的行为。

麦麦KTV 官方网站与文档