分享

如何用js完美的解析lrc歌词

 赶路听风 2018-08-11

如何用js完美的解析lrc歌词


要想解析lrc,就得先知道lrc是什么,lrc是英文lyric(歌词)的缩写,基于纯文本的歌词专用格式,被用做歌词文件的扩展名。以lrc为扩展名的歌词文件可以在各类数码播放器中同步显示,最早是由郭祥祥先生(Djohan)提出并在其程序中得到应用。目前市场上的支持显示歌词的音乐播放器都有相同的规定,要求歌曲和LRC歌词的文件名相同,即:唐磊 - 丁香花.mp3,唐磊 - 丁香花.lrc。


除了lrc格式外,还有qrc(QQ音乐)、krc(酷狗)等,与lrc不同的是 它们的准确性 要 更强。lrc只能精确到每句歌词,而qrc 和 krc 能 精确到每一个字。(可以扩展lrc的 功能 使他精确到每一个字,以后再说)


lrc歌词文本中含有两类标签:

一是标识标签,其格式为“[标识名:值]”主要包含以下预定义的标签:

[ar:艺人名]
[ti:曲名]
[al:专辑名]
[by:编者(指编辑LRC歌词的人)]
[offset:时间补偿值] 其单位是毫秒,正值表示整体提前,负值相反。这是用于总体调整显示快慢的。当用户听歌时,发现歌词快了几秒,可以调整歌词,最后此值被保存在 offset,下次播放时,将自动匹配正确时间。

二是时间标签,形式为“[mm:ss]”或“[mm:ss.ff]”(分钟数:秒数.毫秒数),时间标签需位于某行歌词中的句首部分,一行歌词可以包含多个时间标签(比如歌词中的迭句部分)。当歌曲播放到达某一时间点时,MP3就会寻找对应的时间标签并显示标签后面的歌词文本,这样就完成了“歌词同步”的功能。如以下歌词:

[ti:有谁能够一夜之间长大]
[ar:戊道子]
[al:有谁能够一夜之间长大]
[by:珍妮]

匹配时间为: 04 分 44 秒 的歌曲
[00:00]我爱歌词网 [www.5ilrc.com]
[00:00.90]有谁能够一夜之间长大 - 戊道子
[00:07.10]词:[盘子]
[00:10.99]曲:[陈绍楠]
[00:13.11]编曲:[SEVEN]
[00:16.76][00:17]
[00:35.44][02:09]走去忘记 旧的人旧的自己
[00:42.60][02:16.16]远离回忆 找个人说我爱你
[00:49.79][02:23.22]别带着沉重去飞行
[00:53.31][02:26.68]别怀疑内心深处的勇气
[00:56.88][02:30.34]放纵地拥抱晨曦
[01:00.16][02:33.61]在路上遇见新的自己
[01:06.52][03:11.60]有谁能够一夜之间长大
[01:13.65][03:18.93]爱情碾过还能喘气就不算差
[01:20.73][03:25.99]何必羡慕那不凋败的塑料花
[01:27.66][03:32.96]没有花期不会枯萎 难道美吗
[01:34.76][03:40]尽情亲吻爱情留下的疤
[01:41.66][03:46.96]童话也不只是有水晶鞋和白马
[01:49.01][03:54.40]何必为了一段插曲哭到沙哑
[01:55.91][04:01.08]过程不留遗憾结果也就伟大
[04:13.18]Lrc By:珍妮 QQ:929964514


lrc的写法比较自由,上诉也可写成:

匹配时间为: 04 分 44 秒 的歌曲
[00:00]我爱歌词网 [www.5ilrc.com]
[00:00.9]有谁能够一夜之间长大 - 戊道子
[00:07.1]词:[盘子]
[00:10.99]曲:[陈绍楠]
[00:13.11]编曲:[SEVEN]
[00:16.76]

[00:17]

这是一句废话,解析lrc的时候要过滤
[00:35.44][2:9]走去忘记 旧的人旧的自己[00:42.60][02:16.16]远离回忆 找个人说我爱你
[00:49.79][2:23.22]别带着沉重去飞行[00:53.31][02:26.68]别怀疑内心深处的勇气
[00:56.88][2:30.34]放纵地拥抱晨曦
[1:00.16][2:33.61]在路上遇见新的自己

[ar:戊道子]
[al:有谁能够一夜之间长大][by:珍妮]
[1:6.52][3:11.60]有谁能够一夜之间长大[01:13.65][03:18.93]爱情碾过还能喘气就不算差
[1:20.73][3:25.99]何必羡慕那不凋败的塑料花[01:27.66][03:32.96]没有花期不会枯萎 难道美吗
[01:34.76][03:40]尽情亲吻爱情留下的疤[01:41.66][03:46.96]童话也不只是有水晶鞋和白马

[ti:有谁能够一夜之间长大]
[01:49.01][03:54.40]何必为了一段插曲哭到沙哑[01:55.91][04:01.08]过程不留遗憾结果也就伟大
[04:13.18]Lrc By:珍妮 QQ:929964514


可以看到,预定义标签被打散放进了歌词标签中间,[00:16.76] [00:17] 标签没有歌词,(你们肯定要说[00:17]标签下面不是有句废话么,但是他们中间隔了一个换行符,换行符表示结束,所以说下面那句是废话,解析lrc的时候要过滤掉)没有歌词的时间标签属于无意义的标签,也要去掉。在歌词开头也有一句废话,也要过滤掉。

[01:06:52]被写成了[1:6.52],[02:09]写成了[2:9],写法多种多样,解析lrc歌词的时候一定要注意。



下面直接贴代码(本程序能真正做到完美的解析lrc,并扩展lrc):

先新建一个html文件,记得引jquery

<div class="lyricPanel"></div>


在新建一个css文件:

  1. .lyricPanel
  2. {
  3. width:250px;
  4. height:400px;
  5. border:1px solid red;
  6. overflow-y:scroll;
  7. }

最后加入以下js,运行便能看见效果:

  1. var s = "[ti:有谁能够一夜之间长大]" +
  2. "[al:有谁能够一夜之间长大]" +
  3. "匹配时间为: 04 分 44 秒 的歌曲" +
  4. "[00:00]我爱歌词网 [www.5ilrc.com]"+
  5. "[00:00.90]有谁能够一夜之间长大 - 戊道子"+
  6. "[00:07.10]词:[盘子]"+
  7. "[00:10.99]曲:[陈绍楠]"+
  8. "[00:13.11]编曲:[SEVEN]"+
  9. "[00:17]"+"\n"+
  10. "[00:35.44][02:09]走去忘记 旧的人旧的自己"+
  11. "[00:42.60][02:16.16]远离回忆 找个人说我爱你"+
  12. "[ar:戊道子]" +
  13. "[00:49.79][02:23.22]别带着沉重去飞行"+
  14. "[00:53.31][02:26.68]别怀疑内心深处的勇气"+
  15. "[00:56.88][02:30.34]放纵地拥抱晨曦"+
  16. "[by:珍妮]" +
  17. "[01:00.16][02:33.61]在路上遇见新的自己"+
  18. "[01:06.52][03:11.60]有谁能够一夜之间长大"+
  19. "[01:13.65][03:18.93]爱情碾过还能喘气就不算差"+
  20. "[01:20.73][03:25.99]何必羡慕那不凋败的塑料花"+
  21. "[01:27.66][03:32.96]没有花期不会枯萎 难道美吗"+
  22. "[01:34.76][03:40]尽情亲吻爱情留下的疤"+
  23. "[01:41.66][03:46.96]童话也不只是有水晶鞋和白马"+
  24. "[01:49.01][03:54.40]何必为了一段插曲哭到沙哑"+
  25. "[01:55.91][04:01.08]过程不留遗憾结果也就伟大"+
  26. "[04:13.18]Lrc By:珍妮 QQ:929964514";
  27. if(typeof binlyric != 'object') {binlyric = {};}
  28. binlyric = {
  29. edition:"1.1",
  30. obj:"",
  31. lyricCSS:new Object(),
  32. txt:"",
  33. index:0,
  34. time:new Array(),
  35. lyric:new Array(),
  36. sort:function(){ // 冒泡排序(从小到大)
  37. var third;
  38. for(var j=0;j<this.index-1;j++)
  39. {
  40. for(var i=0;i<this.index-1;i++)
  41. {
  42. if(this.time[i]>this.time[i+1])
  43. {
  44. third = this.time[i];
  45. this.time[i] = this.time[i+1];
  46. this.time[i+1] = third;
  47. third = this.lyric[i];
  48. this.lyric[i] = this.lyric[i+1];
  49. this.lyric[i+1] = third;
  50. }
  51. }
  52. }
  53. },
  54. createPanel:function(){ // 创建歌词面板
  55. var i=0;
  56. $(this.obj).html("");
  57. for(i=0;i<this.index;i++)
  58. {
  59. $(this.obj).append("<div>"+this.lyric[i]+"</div>");
  60. }
  61. for(i in this.lyricCSS)
  62. {
  63. $(this.obj).find("div").css(this.lyricCSS,this.lyricCSS[i]);
  64. }
  65. },
  66. findTags:function(index,strArray,number){ // 查找标签(包括任何扩展的标签)
  67. // 此方法能匹配所有格式的标签
  68. // 因为此方法是在后面写的,所以时间标签并没有使用此方法
  69. number = number || this.txt.length;
  70. number = (number>this.txt.length) ? this.txt.length:number;
  71. var i,j,complete=0,value;
  72. var obj = new Object();
  73. obj.booble = false;
  74. obj.value = "[";
  75. for(i=index;i<number;i++)
  76. {
  77. if(this.txt.substr(i,1)==strArray[complete].s)
  78. {
  79. complete+=1;
  80. if(complete>1)
  81. {
  82. if(complete<strArray.length)
  83. {
  84. obj.value += '{value:"'+this.txt.substr(value+1,i-value-1)+'"},';
  85. }
  86. else
  87. {
  88. obj.value += '{value:"'+this.txt.substr(value+1,i-value-1)+'"}]';
  89. }
  90. }
  91. if(complete==strArray.length)
  92. {
  93. obj.txt = this.txt.substr(index,i-index+1);
  94. obj.value = eval('('+obj.value+')');
  95. obj.index = i+1;
  96. obj.booble = true;
  97. break
  98. }
  99. value = i;
  100. }
  101. else if(this.txt.substr(i,1)=="\n")
  102. {
  103. obj.booble = false;
  104. return obj;
  105. }
  106. else if(this.txt.substr(i,1)==strArray[0].s && complete>0) // 遇到2次开始标志就退出
  107. {
  108. obj.booble = false;
  109. return obj;
  110. }
  111. }
  112. return obj;
  113. },
  114. findlyric:function(index){ // 查找歌词: 有则返回 歌词、继续查找的位置, 否则只返回继续查找的位置
  115. var obj = {};
  116. var str = this.txt;
  117. var i;
  118. for(i=index;i<str.length;i++)
  119. {
  120. if(str.charAt(i)=="[")
  121. {
  122. var _obj = this.findTags(i,[{s:"["},{s:":"},{s:"]"}]);
  123. if(_obj.booble)
  124. {
  125. obj.index = i;//i + _obj.txt.length;
  126. obj.lyric = str.substr(index,i-index);
  127. return obj;
  128. }
  129. }
  130. else if(str.charAt(i)=="\n")
  131. {
  132. obj.index = i+1;
  133. obj.lyric = str.substr(index,i-index);
  134. return obj
  135. }
  136. }
  137. if(i==str.length) // 专处理最后一句歌词(最后一句歌词比较特殊)
  138. {
  139. obj.index = i+1;
  140. obj.lyric = str.substr(index,i-index);
  141. return obj;
  142. }
  143. obj.index = i;
  144. return obj;
  145. },
  146. findTime:function(index){ // 查找时间 : 有则返回 时间、继续查找的位置, 否则只返回继续查找的位置
  147. // 此功能可以用 findTags 方法实现,更简单、更强大、代码更少
  148. // findTags方法 是在后面写的,所以这里就不改了,具体可参考 findID方法里的使用实例
  149. var obj = {};
  150. var thisobj = this;
  151. var str = this.txt;
  152. obj.index = index;
  153. function recursion()
  154. {
  155. var _obj = thisobj.findTime(obj.index);
  156. if(_obj.time)
  157. {
  158. obj.time += _obj.time;
  159. obj.index = _obj.index;
  160. }
  161. }
  162. // --------------- 可以在这里 扩展 其它功能 ---------------
  163. // lrc歌词只能精确到每句歌词,可以通过扩展lrc 精确 到 每个字
  164. if(/\[\d{1,2}\:\d{1,2}\.\d{1,2}\]/.test(str.substr(index,10))) // [mm:ss.ff]
  165. {
  166. obj.time = str.substr(index+1,8) + "|";
  167. obj.index = index+9+1;
  168. recursion();
  169. }
  170. else if(/\[\d{1,2}\:\d{1,2}\]/.test(str.substr(index,7))) // [mm:ss]
  171. {
  172. obj.time = str.substr(index+1,5) + ".00" + "|";
  173. obj.index = index+6+1;
  174. recursion();
  175. }
  176. // 以下标签均属于合法标签,但很少被使用,请根据需要进行扩展
  177. // [mm:ss.f] [mm:s.ff] [mm:s.f] [m:ss.ff] [m:s.ff] [m:s.f]
  178. // [mm:s] [m:ss] [s:s]
  179. return obj;
  180. },
  181. findID:function(index){ // 查找预定义标识
  182. //[ar:艺人名]
  183. //[ti:曲名]
  184. //[al:专辑名]
  185. //[by:编者(指编辑LRC歌词的人)]
  186. //[offset:时间补偿值] 其单位是毫秒,正值表示整体提前,负值相反。这是用于总体调整显示快慢的。(很少被使用)
  187. // 注:本程序也不支持 offset 功能(但是能取值),如需要 请自行在 sort 方法添加此功能
  188. // 此处功能 使用 findTags方法 实现
  189. var obj;
  190. obj = this.findTags(index,[{s:"["},{s:":"},{s:"]"}]);
  191. if(obj.booble)
  192. {
  193. if(obj.value[0].value=="ar")
  194. {
  195. this.ar = obj.value[1].value;
  196. }
  197. else if(obj.value[0].value=="ti")
  198. {
  199. this.ti = obj.value[1].value;
  200. }
  201. else if(obj.value[0].value=="al")
  202. {
  203. this.al = obj.value[1].value;
  204. }
  205. else if(obj.value[0].value=="by")
  206. {
  207. this.by = obj.value[1].value;
  208. }
  209. else if(obj.value[0].value=="offset") // 这里是 offset 的值
  210. {
  211. this.offset = obj.value[1].value;
  212. }
  213. }
  214. },
  215. analysis:function(){ // 解析
  216. if(this.txt=="") return false;
  217. var str = this.txt;
  218. this.index = 0;
  219. for(var i=0;i<str.length;i++)
  220. {
  221. if(str.charAt(i)=="[")
  222. {
  223. var time = this.findTime(i);
  224. if(time.time) // 时间标签
  225. {
  226. var lyric = this.findlyric(time.index);
  227. if(lyric.lyric!="\n" && lyric.lyric!="") // 去掉无意义歌词
  228. {
  229. var timeArray = time.time.split("|");
  230. for(var j=0;j<timeArray.length;j++)
  231. {
  232. if(timeArray[j])
  233. {
  234. this.time[this.index] = timeArray[j];
  235. this.lyric[this.index] = lyric.lyric;
  236. this.index+=1;
  237. }
  238. }
  239. }
  240. i = time.index;
  241. }
  242. else // 预定义标签
  243. {
  244. this.findID(i);
  245. }
  246. }
  247. }
  248. this.sort();
  249. this.createPanel();
  250. },
  251. play:function(position,CSS){ // 定位指定时间的歌词
  252. var time;
  253. var obj = this;
  254. function set(index)
  255. {
  256. var height = parseInt($(obj.obj).find("div").css("height"));
  257. var top = parseInt($(obj.obj).find("div").css("margin-top"));
  258. $(obj.obj).animate({
  259. scrollTop:(index*height+index*top-parseInt($(obj.obj).css("height"))/2+height/2)
  260. },300);
  261. for(var i in CSS)
  262. {
  263. $(obj.obj).find("div").eq(index).css(CSS,CSS[i]);
  264. }
  265. }
  266. for(var i=0;i<this.index;i++)
  267. {
  268. if(position==this.time[i])
  269. {
  270. Set(i);
  271. return;
  272. }
  273. else if(position>this.time[i])
  274. {
  275. time = i;
  276. }
  277. }
  278. set(time);// 没找到匹配时间 则就近最小选择
  279. }
  280. };
  281. binlyric.txt = s;
  282. binlyric.obj = ".lyricPanel";
  283. binlyric.lyricCSS = {"font-size":"16px","margin-top":"15px","text-align":"center"};
  284. binlyric.analysis();
  285. binlyric.play("01:20.22",{
  286. color:"red"
  287. });
  288. alert("艺人名:"+binlyric.ar);
  289. alert("专辑名:"+binlyric.al);
  290. alert("歌词编者:"+binlyric.by);
  291. alert("歌曲名:"+binlyric.ti);

发现上面有正则表达的地方 显示出问题了,大家直接下源码吧,源码上有注释。

源码下载地址:http://pan.baidu.com/s/1eRrKwHs

效果图:


原生js实现音乐播放器功能,可以实时显示歌词并且高亮当前句


最近在做项目,总结一下学到的知识, 
首先就是一个简单的音乐播放器。可以显示歌词,并且突出显示当前播放的这一句。 
但是歌词必须是要转码成utf-8的,不然是乱码,后面提供我淘到的转码工具,非常简单,把下载的lrc格式的歌词文件拖到这个软件上他就提醒你转码完毕,这时候就可以用了。 
html,css,js我都放上去,js放到最后,前面两个我就不解释了,各位看官如果想直接看js就拖到最后吧。这是从项目里面提取的代码,用webpack,所以引用什么的就劳烦自己加上去~,具体功能请看html,有的功能还没有做,做了再更新,,,想了想代码还是放在github吧。。。代码有点长na~ 
简单粗暴先看一下效果图。 
一首分手快乐送给大家 
这里写图片描述 
这里写图片描述 
我的资源页,不知道可不可以打开((/ □ ))传送门

本文属原创,转载请注明。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多