拟声云歌词
- 拟声服务器的歌词是由用户上传或制作的
- 我们已经尝试过利用 mysql 的全文索引实现匹配,尽管开启了 mysql 的中文支持,但很多时候匹配结果偏差很大:
- 一方面是用户上传时,歌词的标题可能自己取的,也可能是跟随歌曲的标题,有些音源平台,如 bili 下载的歌曲标题往往混有 【4K超清】、【HR无损】、 某句歌词 等各种冗余信息,这些词容易导致匹配错误
- 标题中混杂歌手名,和上一条问题类似,容易导致匹配到同一歌手的不同歌曲上
- 以及英文短语匹配问题,英文词句切分后匹配也容易匹配错误,比如
I LOVE U在被切分为三个词后匹配,经常匹配到love love love之类的标题上 - 由于歌词量积累了几十万,全文索引占用了很多内存,多次优化后仍然无解
- 25年中时,拟声在客户端初步实现了
智能识别功能,着重解决从混杂的标题中提取 真正的歌曲标题、移除无效词、歌手列表、关联歌手常见的别名、繁简体、错别字。刚好最近重构服务端的歌词匹配功能时,换用自己实现分词、搜索,并将智能识别迁移到了服务端上实现,简单来看流程为:- 服务端启动时读取数据,调用智能识别提取标题、歌手列表,对标题进行中英文分词,添加分词结果和歌手列表到搜索引擎中
- 对标题、歌手、不同版本(如 DJ、remix)设置不同的字段权重,强化标题的搜索得分优先级
- 搜索请求来临时,调用智能识别对搜索词进行提取,再分词,然后传入搜索引擎进行 BM25 相关性计算得分,返回关联性高的结果
- 服务端运行期间,增删改歌词时同步调整搜索引擎内的分词记录
- 目前成效显著,且性能大幅提高,内存占用也小了很多;当然也有一些问题有待解决,如果后续歌词量大幅增加,需要考虑将索引从内存放一部分到硬盘中缓解内存压力
- 歌词匹配存在违禁词检查,如果上传的歌词包含违禁词,将不会被搜索到;但上传者可以查看,或是直接检索对应歌词的 lrcid
Api接口
搜索歌词
- URL:
https://api.music.bool.run/api/search/lyric - 方法:
GET - 参数:
info: 可选;搜索词、短语,可以混杂 歌曲标题、歌手,服务端会执行智能识别和分词。限制最大 1000 字节,超过部分会被截断舍弃str: 可选;和info完全一致,二选一或都不传入即可title: 可选;歌曲标题,不建议这里混杂歌手名artist: 可选;歌手列表,可以传入多个歌手,用中英文的逗号、顿号、分号等方式隔开均可duration: 可选;歌曲时长(总毫秒数),指定可能有助于搜索更准确- 注:
info、str、title必须至少传入一个info和str同时指定时取str,舍弃infoinfo和title同时指定是,会从info中识别 {标题}和{歌手},然后 {标题}拼接title进行搜索
- 示例:
sh
GET https://api.music.bool.run/api/search/lyric?info=%E5%B9%B2%E7%89%A9%E5%A5%B3-%E6%B4%9B%E5%A4%A9%E4%BE%9D- 响应:
code: 响应码,成功为2000;其他见 响应码data:Array<int>lrcid数组,一般最大20个tip: 错误提示- 示例:
json
{
"code": 2000,
"data": [
1230669,
1164209,
1030869,
1235425,
1206788
],
"tip": ""
}获取歌词内容
- URL:
https://api.music.bool.run/api/lyric/get/src - 方法:
GET - 参数:
lrcid: 必选;歌词IDcache_time: 可选;客户端的歌词内容毫秒缓存时间戳,如果指定,服务器会对比参数和数据库中这条歌词内容的最后更新时间,如果认为客户端缓存仍然可用,将不返回歌词内容,且响应码为2304,即为请客户端直接使用缓存即可src_type: 可选;默认json,取值以下之一:json: 返回json格式的歌词,根据源歌词类型,携带的时间戳格式支持无时间戳、逐行、逐字lrc: 返回逐行格式的歌词,如果源歌词为逐字歌词,也将转为逐行歌词返回,可能存在无时间戳的LRC歌词lrc_word: 返回逐字格式的歌词,符合增强型LRC歌词格式。如果源歌词为逐行、无时间戳的歌词,则返回对应的逐行、无时间戳的增强型LRC歌词- 示例:
sh
GET https://api.music.bool.run/api/lyric/get/src?lrcid=1227613&src_type=json- 响应:
code: 响应码,成功为2000;其他见 响应码data: 歌词内容,根据src_type指定的类型返回json、lrc、增强型LRC格式tip: 错误提示- 示例:
json
{
"code": 2000,
"data": "{\"lrc\": [{\"time\": 1.507, \"content\": \"是想你的声音啊 (DJ)\", \"timelist\": [{\"time\": 1.507, \"index\": 0}, {\"time\": 1.596, \"index\": 1}, {\"time\": 1.926, \"index\": 2}, {\"time\": 2.11, \"index\": 3}, {\"time\": 2.438, \"index\": 4}, {\"time\": 2.516, \"index\": 5}, {\"time\": 2.953, \"index\": 6}, {\"time\": 3.048, \"index\": 9}, {\"time\": 4.083, \"index\": 12}]}, {\"time\": 5.36, \"content\": \"随这种感觉走\", \"timelist\": [{\"time\": 5.36, \"index\": 0}, {\"time\": 5.435, \"index\": 1}, {\"time\": 5.779, \"index\": 2}, {\"time\": 5.888, \"index\": 3}, {\"time\": 6.198, \"index\": 4}, {\"time\": 6.464, \"index\": 5}, {\"time\": 7.276, \"index\": 6}]}, {\"time\": 180.363, \"content\": \"难道这不是你最爱的天气\", \"timelist\": [{\"time\": 180.363, \"index\": 0}, {\"time\": 180.644, \"index\": 1}, {\"time\": 181.288, \"index\": 2}, {\"time\": 182.479, \"index\": 3}, {\"time\": 182.594, \"index\": 4}, {\"time\": 183.053, \"index\": 5}, {\"time\": 183.21, \"index\": 6}, {\"time\": 183.725, \"index\": 7}, {\"time\": 183.832, \"index\": 8}, {\"time\": 184.519, \"index\": 9}, {\"time\": 185.074, \"index\": 10}, {\"time\": 187.31, \"index\": 11}]}], \"timeType\": 1}",
"tip": ""
}- 其中,json格式的
data内容格式:timeType:int歌词时间戳类型,部分数据未更新可能不准确,取值:- 0: 未知
- 1: 逐字
- 2: 逐行
info:Map<String, String>歌词标签信息,对应 LRC格式中开头常见的[offset:0]、[ti:是想你的声音啊]等标签lrc:Array<LrcItem>歌词内容数组,每一个为一行歌词,LrcItem 内容:time: 这一行歌词的开始时间content: 这一行的歌词内容timelist:Array<LrcTime>歌词的逐字时间戳列表;如果源歌词不是逐字歌词,该字段数组内容可能少于2个。每一个为一个字或词的开始时间,LrcTime 内容:time: 这个字或单词的开始时间index: 这个字或单词在content作为UTF字符串中的开始下标,此时无论中英文字符长度都认为是 1。一般最后一个的index为content的长度,代表最后一个字的结束时间。
json
{
"timeType": 1,
"info": {
"offset": 0,
"ti": "是想你的声音啊",
},
"lrc": [
{
"time": 1.507,
"content": "一行歌词内容",
"timelist": [
{"time": 1.507, "index": 0},
{"time": 1.596, "index": 1},
{"time": 1.926, "index": 2},
{"time": 2.11, "index": 6}
],
}
],
}- dart/flutter 歌词内容 json/lrc 解析实现:lyric_xx
响应码
2000: 成功2304: 成功,但服务器并不返回内容,请使用缓存2777: 成功,并附带提示3777: 跳转标记,并附带提示4000: 用户需要登录4001: 权限错误4003: 拒绝请求4004: 请求资源无法找到4010: 参数缺失4011: 参数错误,指定了参数但是不符合要求,类型错误或是超出限定的数值范围、枚举范围4012: 已被删除4013: 重复动作4014: 已被封禁4015: 违反限制4016: 访问太快4017: 访问次数太多4018: 验证码等验证方式不通过4019: 信用评估较低,不可使用4020: 不允许的操作4021: 已过期4022: 无效4023: 需要等待客户端进一步操作4024: 废弃的api,建议更新4777: 客户端错误,并附带提示4900: 失败5000: 服务器错误5001: 需要等待服务端进一步操作5100: 外部服务器错误5777: 服务端错误,并附带提示