UTF8的编码问题只解决了一半,待更新
http://www.jxva.com/blog/201112/59.html
LRC歌词的概念不用再解释了,详情可以参见百度百科对LRC歌词的解释http://baike.baidu.com/view/239396.htm。本文主要介绍AS版本的LRC歌词的解析。
主要分为两个部分作为介绍:
- 文本文件的编码方式识别(ANSI, UTF-8, Unicode等)
- 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; } } }
未来会带来时间轴精确到字的歌词文件的格式和内容解析
其它文件和整个工程的下载地址为