1. 程式人生 > >如何用js完美的解析lrc歌詞

如何用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檔案:
.lyricPanel
{
	width:250px;
	height:400px;
	border:1px solid red;
	overflow-y:scroll;
}

最後加入以下js,執行便能看見效果:
var s = "[ti:有誰能夠一夜之間長大]" +
"[al:有誰能夠一夜之間長大]" +
"匹配時間為: 04 分 44 秒 的歌曲" +
"[00:00]我愛歌詞網 [www.5ilrc.com]"+
"[00:00.90]有誰能夠一夜之間長大 - 戊道子"+
"[00:07.10]詞:[盤子]"+
"[00:10.99]曲:[陳紹楠]"+
"[00:13.11]編曲:[SEVEN]"+
"[00:17]"+"\n"+
"[00:35.44][02:09]走去忘記 舊的人舊的自己"+
"[00:42.60][02:16.16]遠離回憶 找個人說我愛你"+
"[ar:戊道子]" +
"[00:49.79][02:23.22]別帶著沉重去飛行"+
"[00:53.31][02:26.68]別懷疑內心深處的勇氣"+
"[00:56.88][02:30.34]放縱地擁抱晨曦"+
"[by:珍妮]" +
"[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";


if(typeof binlyric != 'object') {binlyric = {};}
binlyric = {
	edition:"1.1",
	obj:"",
	lyricCSS:new Object(),
	txt:"",
	index:0,
	time:new Array(),
	lyric:new Array(),
	sort:function(){ // 氣泡排序(從小到大)
		var third;
		for(var j=0;j<this.index-1;j++)
		{
			for(var i=0;i<this.index-1;i++)
			{
				if(this.time[i]>this.time[i+1])
				{
					third = this.time[i];
					this.time[i] = this.time[i+1];
					this.time[i+1] = third;
					third = this.lyric[i];
					this.lyric[i] = this.lyric[i+1];
					this.lyric[i+1] = third;
				}
			}
		}
	},
	createPanel:function(){ // 建立歌詞面板
		var i=0;
		$(this.obj).html("");
		for(i=0;i<this.index;i++)
		{
			$(this.obj).append("<div>"+this.lyric[i]+"</div>");
		}
		for(i in this.lyricCSS)
		{
			$(this.obj).find("div").css(this.lyricCSS,this.lyricCSS[i]);
		}
	},
	findTags:function(index,strArray,number){ // 查詢標籤(包括任何擴充套件的標籤)
		// 此方法能匹配所有格式的標籤
		// 因為此方法是在後面寫的,所以時間標籤並沒有使用此方法
		number = number || this.txt.length;
		number = (number>this.txt.length) ? this.txt.length:number;
		var i,j,complete=0,value;
		var obj = new Object();
		obj.booble = false;
		obj.value = "[";
		for(i=index;i<number;i++)
		{
			if(this.txt.substr(i,1)==strArray[complete].s)
			{
				complete+=1;
				if(complete>1)
				{
					if(complete<strArray.length)
					{
						obj.value += '{value:"'+this.txt.substr(value+1,i-value-1)+'"},';
					}
					else
					{
						obj.value += '{value:"'+this.txt.substr(value+1,i-value-1)+'"}]';
					}
				}
				if(complete==strArray.length)
				{
					obj.txt = this.txt.substr(index,i-index+1);
					obj.value = eval('('+obj.value+')');
					obj.index = i+1;
					obj.booble = true;
					break
				}
				value = i;
			}
			else if(this.txt.substr(i,1)=="\n")
			{
				obj.booble = false;
				return obj;
			}
			else if(this.txt.substr(i,1)==strArray[0].s && complete>0) // 遇到2次開始標誌就退出
			{
				obj.booble = false;
				return obj;
			}
		}
		return obj;
	},
	findlyric:function(index){ // 查詢歌詞: 有則返回 歌詞、繼續查詢的位置, 否則只返回繼續查詢的位置
		var obj = {};
		var str = this.txt;
		var i;
		for(i=index;i<str.length;i++)
		{
			if(str.charAt(i)=="[")
			{
				var _obj = this.findTags(i,[{s:"["},{s:":"},{s:"]"}]);
				if(_obj.booble)
				{
					obj.index = i;//i + _obj.txt.length;
					obj.lyric = str.substr(index,i-index);
					return obj;
				}
			}
			else if(str.charAt(i)=="\n")
			{
				obj.index = i+1;
				obj.lyric = str.substr(index,i-index);
				return obj
			}
		}
		if(i==str.length) // 專處理最後一句歌詞(最後一句歌詞比較特殊)
		{
			obj.index = i+1;
			obj.lyric = str.substr(index,i-index);
			return obj;
		}
		obj.index = i;
		return obj;
	},
	findTime:function(index){ // 查詢時間 : 有則返回 時間、繼續查詢的位置, 否則只返回繼續查詢的位置
		// 此功能可以用 findTags 方法實現,更簡單、更強大、程式碼更少
		// findTags方法 是在後面寫的,所以這裡就不改了,具體可參考 findID方法裡的使用例項
		var obj = {};
		var thisobj = this;
		var str = this.txt;
		obj.index = index;
		function recursion()
		{
			var _obj = thisobj.findTime(obj.index);
			if(_obj.time)
			{
				obj.time += _obj.time;
				obj.index = _obj.index;
			}
		}
		// --------------- 可以在這裡 擴充套件 其它功能 ---------------
		// lrc歌詞只能精確到每句歌詞,可以通過擴充套件lrc 精確 到 每個字
		if(/\[\d{1,2}\:\d{1,2}\.\d{1,2}\]/.test(str.substr(index,10))) // [mm:ss.ff]
		{
			obj.time = str.substr(index+1,8) + "|";
			obj.index = index+9+1;
			recursion();
		}
		else if(/\[\d{1,2}\:\d{1,2}\]/.test(str.substr(index,7))) // [mm:ss]
		{
			obj.time = str.substr(index+1,5) + ".00" + "|";
			obj.index = index+6+1;
			recursion();
		}
		// 以下標籤均屬於合法標籤,但很少被使用,請根據需要進行擴充套件
		// [mm:ss.f] [mm:s.ff] [mm:s.f] [m:ss.ff] [m:s.ff] [m:s.f]
		// [mm:s] [m:ss] [s:s]
		return obj;
	},
	findID:function(index){ // 查詢預定義標識
		//[ar:藝人名]
		//[ti:曲名]
		//[al:專輯名]
		//[by:編者(指編輯LRC歌詞的人)]
		//[offset:時間補償值] 其單位是毫秒,正值表示整體提前,負值相反。這是用於總體調整顯示快慢的。(很少被使用)
		// 注:本程式也不支援 offset 功能(但是能取值),如需要 請自行在 sort 方法新增此功能
		// 此處功能 使用 findTags方法 實現
		var obj;
		obj = this.findTags(index,[{s:"["},{s:":"},{s:"]"}]);
		if(obj.booble)
		{
			if(obj.value[0].value=="ar")
			{
				this.ar = obj.value[1].value;
			}
			else if(obj.value[0].value=="ti")
			{
				this.ti = obj.value[1].value;
			}
			else if(obj.value[0].value=="al")
			{
				this.al = obj.value[1].value;
			}
			else if(obj.value[0].value=="by")
			{
				this.by = obj.value[1].value;
			}
			else if(obj.value[0].value=="offset") // 這裡是 offset 的值
			{
				this.offset = obj.value[1].value;
			}
		}
	},
	analysis:function(){ // 解析
		if(this.txt=="") return false;
		var str = this.txt;
		this.index = 0;
		for(var i=0;i<str.length;i++)
		{
			if(str.charAt(i)=="[")
			{
				var time = this.findTime(i); 
				if(time.time) // 時間標籤
				{
					var lyric = this.findlyric(time.index);
					if(lyric.lyric!="\n" && lyric.lyric!="") // 去掉無意義歌詞
					{
						var timeArray = time.time.split("|");
						for(var j=0;j<timeArray.length;j++)
						{
							if(timeArray[j])
							{
								this.time[this.index] = timeArray[j];
								this.lyric[this.index] = lyric.lyric;
								this.index+=1;
							}
						}
					}
					i = time.index;
				}
				else // 預定義標籤
				{
					this.findID(i);
				}
			}
		}
		this.sort();
		this.createPanel();
	},
	play:function(position,CSS){ // 定位指定時間的歌詞
		var time;
		var obj = this;
		function set(index)
		{
			var height = parseInt($(obj.obj).find("div").css("height"));
			var top = parseInt($(obj.obj).find("div").css("margin-top"));
			$(obj.obj).animate({
				scrollTop:(index*height+index*top-parseInt($(obj.obj).css("height"))/2+height/2)
			},300);
			for(var i in CSS)
			{
				$(obj.obj).find("div").eq(index).css(CSS,CSS[i]);
			}
		}
		for(var i=0;i<this.index;i++)
		{
			if(position==this.time[i])
			{
				Set(i);
				return;
			}
			else if(position>this.time[i])
			{
				time = i;
			}
		}
		set(time);// 沒找到匹配時間 則就近最小選擇
	}
};

binlyric.txt = s;
binlyric.obj = ".lyricPanel";
binlyric.lyricCSS = {"font-size":"16px","margin-top":"15px","text-align":"center"};
binlyric.analysis();
binlyric.play("01:20.22",{
	color:"red"
});
alert("藝人名:"+binlyric.ar);
alert("專輯名:"+binlyric.al);
alert("歌詞編者:"+binlyric.by);
alert("歌曲名:"+binlyric.ti);

發現上面有正則表達的地方 顯示出問題了,大家直接下原始碼吧,原始碼上有註釋。

效果圖:

本文屬原創,轉載請註明。

另外,求一份 web前端的工作(成都),QQ:2190460780。