ActionScript LRC歌词解析(兼容ANSI, UTF-8, Unicode等编码)

时间:2023-01-05 10:41:47

UTF8的编码问题只解决了一半,待更新

http://www.jxva.com/blog/201112/59.html

   

 

LRC歌词的概念不用再解释了,详情可以参见百度百科对LRC歌词的解释http://baike.baidu.com/view/239396.htm。本文主要介绍AS版本的LRC歌词的解析。

    主要分为两个部分作为介绍:

  1. 文本文件的编码方式识别(ANSI, UTF-8, Unicode等)
  2. LRC歌词文本的解析

    一个典型的LRC文件的示例

 1 [ti:曹操]
 2 [ar:林俊杰]
 3 [al:曹操]
 4 
 5 [00:00.03]曹操
 6 [00:03.46]作词:林秋离
 7 [00:10.44]作曲:林俊杰
 8 [00:13.35]演唱:林俊杰
 9 [00:20.12]
10 [01:33.46][00:26.82]不是英雄 不读三国
11 [01:40.12][00:33.43]若是英雄 怎么能不懂寂寞
12 [02:39.68][01:46.34][00:39.63]独自走下长坂坡 月光太温柔
13 [02:43.20][01:49.82][00:43.15]曹操不啰嗦 一心要那荆州
14 [02:46.75][01:53.48][00:46.83]用阴谋 阳谋 明说 暗夺的摸
15 [02:53.44][02:00.10][00:53.50]东汉末年分三国
16 [02:56.37][02:03.15][00:56.52]烽火连天不休
17 [03:00.12][02:06.75][01:00.17]儿女情长 被乱世左右
18 [03:05.04][02:11.71][01:05.12]谁来煮酒
19 [03:06.78][02:13.45][01:06.84]尔虞我诈是三国
20 [03:09.85][02:16.43][01:09.73]说不清对与错
21 [03:13.38][02:20.11][01:13.48]纷纷扰扰 千百年以後
22 [03:18.44][02:25.06][01:18.45]一切又从头
23 [03:25.99][02:30.17][01:26.81]喔
24 [03:33.44]

 

文本文件的编码方式识别

    文本文件的头几个字符告知此文件是什么类型,因此读进去文件之后可以取出这几个字符加以判断,DataManager.as能够实现字节数组到字符串的转换,代码如下

package org.itec.tianyu 
{
	import flash.events.EventDispatcher;
	import flash.events.Event;
	import flash.net.URLStream;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;
	
	/**
	 * 数据中心类
	 * @author Tian Yu
	 */
	public class DataManager extends EventDispatcher
	{
		private const ANSI:String = "ANSI";
		private const UNICODE:String = "Unicode";
		private const UNICODE_BIG_ENDIAN:String = "Unicode big endian";
		private const UTF8:String = "UTF-8";
		
		private var ur:URLRequest;
		private var us:URLStream;
		
		private var charset:String = ANSI; // 默认是ANSI编码
		private var ba:ByteArray;
		private var str:String;
		
		public static const DATA_PREPARED:String = "data_prepared";
		
		/**
		 * 从网络地址获取数据
		 * @param path 网络地址
		 */
		public function load(path:String):void
		{
			ur = new URLRequest(path);
			us = new URLStream();
			us.addEventListener(Event.COMPLETE, usCompleted);
			us.load(ur);
		}
		
		private function usCompleted(event : Event) : void 
		{
			ba = new ByteArray();
			us.removeEventListener(Event.COMPLETE, usCompleted);
			us.readBytes(ba);
			us.close();
			us = null;
			ur = null;
			str = bytesToString(ba);
			
			dispatchEvent(new Event(DATA_PREPARED));
		}
		
		/**
		 * 判断编码,并将ByteArray转换成String
		 */
		private function bytesToString(bytearray:ByteArray):String
		{
			var baString:String = null;
			var baLength:uint = bytearray.length;
			
			var UNICODES:Array = [0xFF, 0xFE];
			var UNICODE_BIG_ENDIANS:Array = [0xFE, 0xFF];
			var UTF8S:Array = [0xEF, 0xBB, 0xBF];
			
			// 判断编码
			bytearray.position = 0;
			var byte1:int = bytearray.readUnsignedByte();
			var byte2:int = bytearray.readUnsignedByte();
			var byte3:int = bytearray.readUnsignedByte();
			if(byte1 == UNICODES[0] && byte2 == UNICODES[1])
				charset = UNICODE;
			else if(byte1 == UNICODE_BIG_ENDIANS[0] && byte2 == UNICODE_BIG_ENDIANS[1])
				charset = UNICODE_BIG_ENDIAN;
			else if(byte1 == UTF8S[0] && byte2 == UTF8S[1] && byte3 == UTF8S[2])
				charset = UTF8;
			bytearray.position = 0;
			
			if(charset == ANSI)
				baString = bytearray.readMultiByte(baLength, "gb2312");
			else
				baString = bytearray.toString();
			return baString;
			
		}
		
		/**
		 * 返回数据中心的字符串数据
		 */
		override public function toString() : String 
		{
			return str;
		}
		
		/**
		 * 返回数据中心的原始字节数组
		 */
		public function toByteArray():ByteArray
		{
			return ba;
		}
	}
}

  

  对于ANSI编码格式的文件,处理的方式是

bytearray.readMultiByte(baLength, "gb2312");

  对于UTF-8,Unicode等编码方式的文件,处理方式是

baString = bytearray.toString();

  利用以上方法实现从字节数组到字符串的转换。

LRC歌词文本的解析

     LRC歌词的字符串的解析主要包括歌曲信息的解析和歌词内容的解析。

     头文件的解析直接利用正则表达式就可以实现,具体的过程在代码中有说明,比较简单,这里不再解释。

     内容文件需要注意以下格式的内容解析,虽然内容一样,但是时间不一样

[02:39.68][01:46.34][00:39.63]独自走下长坂坡 月光太温柔

      依然是用正则表达式匹配,全部音句解析之后按照开始时间排序就ok了,具体过程见代码

package org.itec.tianyu 
{
	/**
	 * 歌曲类
	 * @author Tian Yu
	 */
	public class Song 
	{
		// 歌曲信息
		private var _songInfo : SongInfo;
		
		// 所有音句的信息
		private var _sentences : Vector.<Sentence>;
		
		public function Song()
		{
			_songInfo = new SongInfo();
			_sentences = new Vector.<Sentence>();
		}
		
		/**
		 * 解析lrc歌词, 返回一个Song的实例
		 * 参考
		 * <a href='http://bbs.9ria.com/thread-12241-1-1.html'>跟kaka做播放器(一)——LRC歌词解析篇 </a>  
		 * @param lrcString 歌词字符串 包含形如<code>[02:39.68][01:46.34][00:39.63]独自走下长坂坡 月光太温柔</code>的歌词
		 * @return Song类的一个实例
		 */
		public static function fromLrc(lrcString:String):Song
		{
			var l : String = lrcString;
			var s : Song = new Song();

			//// 开始获取歌曲信息
			// 歌曲名
			var reg_title : RegExp = /\[ti:(.+?)\]/i;
			// 歌手
			var reg_artist: RegExp = /\[ar:(.+?)\]/i;
			// 专辑
			var reg_album : RegExp = /\[al:(.+?)\]/i;
			// 歌词作者
			var reg_maker : RegExp = /\[(by:.+?)\]/i;
			// 时间补偿值
			var reg_offset: RegExp = /\[offset:(.+?)\]/;

			var arr_title : Array = l.match(reg_title);
			var arr_artist: Array = l.match(reg_artist);
			var arr_album : Array = l.match(reg_album);
			var arr_maker : Array = l.match(reg_maker);
			var arr_offset: Array = l.match(reg_offset);

			if (arr_title != null) {
				s.songInfo.title = arr_title[1];
			}
			if (arr_artist != null) {
				s.songInfo.artist = arr_artist[1];
			}
			if (arr_album != null) {
				s.songInfo.album = arr_album[1];
			}
			if (arr_maker != null) {
				s.songInfo.maker = arr_maker[1];
			}
			if (arr_offset != null) {
				s.songInfo.offset = Number(arr_offset[1]);
			}
			//// 歌曲信息获取完毕

			//// 开始获取歌词信息
			var reg_take : RegExp = /\[\d\d:\d\d\.\d\d\].*/g;

			// 获取歌词,去除歌曲信息
			var arr_lyrics : Array = l.match(reg_take);
			// 过滤时间信息的正则
			var reg_replacetime : RegExp = /(\[\d\d:\d\d\.\d\d\])+/g;
			// 获取时间信息的正则
			var reg_gettimes : RegExp = /\[\d\d:\d\d\.\d\d\]/g;
			var reg_readtime : RegExp = /\[(\d\d):(\d\d\.\d\d)\]/;
			// 均衡歌词间距的正则
			var reg_dis : RegExp = / $/;

			var arr_splitedLyrics : Array = new Array();

			for each (var ly:String in arr_lyrics) {
				// 获取所有的时间信息
				var arr_tmptime : Array = ly.match(reg_gettimes);
				// 去除时间信息,只保留歌词以便后面形成数组
				ly = ly.replace(reg_replacetime, "");
				for (var i : int = 0; i < arr_tmptime.length; i++) {

					// 解析时间为Number
					var t : String = arr_tmptime[i];
					var min : String = t.replace(reg_readtime, "$1");
					var sec : String = t.replace(reg_readtime, "$2");
					var time : Number = Number(min) * 60000 + Number(sec) * 1000;

					// 第二个元素为歌词
					if (!reg_dis.test(ly)) {
						ly += " ";
					}

					var sentence:Sentence = new Sentence();
					sentence.start = (time + s.songInfo.offset > 0) ? time + s.songInfo.offset : 0; // 添加offset的影响
					sentence.words = ly;
					// 将每一行添加进总的歌词数组
					arr_splitedLyrics.push(sentence);
				}
			}
			
            // 歌词排序
            arr_splitedLyrics.sortOn("start", Array.NUMERIC);
			//// 歌词信息获取完毕

			// 音句信息的存储
			for (i = 0; i < arr_splitedLyrics.length; i++) 
			{
				s.sentences.push(arr_splitedLyrics[i]);
			}

			return s;
		}

		public function get songInfo() : SongInfo {	return _songInfo;	}
		public function set songInfo(songInfo : SongInfo) : void {	this._songInfo = songInfo;	}

		public function get sentences() : Vector.<Sentence> {	return _sentences;	}
		public function set sentences(sentences : Vector.<Sentence>) : void {	this._sentences = sentences;	}
		
		
	}
}

  

  未来会带来时间轴精确到字的歌词文件的格式和内容解析

  其它文件和整个工程的下载地址为

http://files.cnblogs.com/tianyuac/LrcResolver.zip