Skip to content

拟声云歌词

  • 拟声服务器的歌词是由用户上传或制作的
  • 我们已经尝试过利用 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: 可选;歌曲时长(总毫秒数),指定可能有助于搜索更准确
    • 注:
      • infostrtitle 必须至少传入一个
      • infostr同时指定时取str,舍弃info
      • infotitle同时指定是,会从 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: 必选;歌词ID
    • cache_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指定的类型返回jsonlrc增强型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。一般最后一个的 indexcontent的长度,代表最后一个字的结束时间。
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: 服务端错误,并附带提示