/** * 文件名:LrcAnalyst.java * 环境: GNU/Linux Ubuntu 7.04 + Eclipse 3.2 + JDK 1.6 * 功能:解析Lrc歌词文件,这只是一个在CLI下的Demo * 版本:0.0.1.0 * 作者:88250 * 日期:2007.4.22 * E-mail & MDN: DL88250@gmail.com * QQ:845765 */ import java.io.*; import java.util.*; /** * 歌词解释类,用于对lrc歌词文件的解析 */ public class LrcAnalystCLI { /** * lrc文件缓冲区 */ private static Vector lyrics; /** * 格式化好的歌词时间与索引,格式参考TimeAndIndex类 */ private static Vector lrcTimeValAndIndex; /** * 歌词的实际内容 */ private static Vector lyrcisContent; /** * 时间标签的长度,用于区分时间标签的格式 * * @see private float computeTimeTag(String timeStr)方法 */ private int timeTagLength; /** * 真正的歌词内容开始到lrc文本开头的偏移量 */ private int realLyrcisStartOffset; /** * 返回真正的歌词内容开始到lrc文本开头的偏移量 * * @return 偏移量 */ public int getRealLrcStartOffset() { return realLyrcisStartOffset; } /** * 是否确定了歌词标签的格式,确定的话是<code>true</code>, 否则是<code>false</code> * * @see private float computeTimeTag(String timeStr)方法 */ private boolean isConfirmTimeTagLeng; /** * 歌词解析器的默认构造器 <br> * 初始化歌词文件缓冲等字段 */ public LrcAnalystCLI() { lyrics = new Vector(); lrcTimeValAndIndex = new Vector(); lyrcisContent = new Vector(); timeTagLength = 0; isConfirmTimeTagLeng = false; realLyrcisStartOffset = 0; } /** * 按行对去读取文本文件内容 * * @param fileName * 文件路径与文件名 * @throws IOException */ public void readFile(String fileName) { try { InputStream r = new FileInputStream(fileName); ByteArrayOutputStream byteout = new ByteArrayOutputStream(); byte tmp[] = new byte[1024]; byte context[]; r.read(tmp); byteout.write(tmp); context = byteout.toByteArray(); String str = new String(context, "GBK"); String lyrcisText[] = str.split("\n"); for (int i = 0; i < lyrcisText.length; i++) { lyrics.add(lyrcisText); } } catch (IOException e) { e.printStackTrace(); } } /** * 显示读取lrc里的所有内容到控制台 */ public void displayLrcContent() { for (int i = 0; i < lyrics.size(); i++) { System.out.println(lyrics.get(i)); } } /** * 从文件缓冲lyrics Vector里进行歌词文件内容的解析,并将解析结果保存 <br> * 解析方法: <br> * Step0. 用']'分割一行内容到String[] Step1. 查看每一行在第3个char是否是':' <br> * Step2. 查看每一行在第6个char是否是'.' <br> * Step3. 确定为歌词行,确定该lrc用的格式 <br> * <b>注意:目前,时间标签的格式有两种,分别是长度为8和5的</b> <br> * Step4. 循环直到歌词缓冲结束 <br> * <b>注意:这个算法可能存在严重缺陷<b> * * @see 关于时间标签的格式,参考TimeTag类 */ public void parseLyrics() { for (int i = 0; i < lyrics.size(); i++) { String aLineLyrics = (String) lyrics.get(i); String[] partsLrc = aLineLyrics.split("]"); char flag1 = 0; char flag2 = 0; if (!aLineLyrics.isEmpty()) {// 很多制作lrc文件的人不负责,乱搞格式!!!! flag1 = aLineLyrics.charAt(1); flag2 = aLineLyrics.charAt(3); } if ((aLineLyrics.length() > 3) && (flag1 >= 48 && flag1 <= 57) && (':' == flag2)) {// 确定为歌词内容行 if (!isConfirmTimeTagLeng && aLineLyrics.length() > 9) { if (aLineLyrics.charAt(9) == ']') {// 长度为8的时间标签格式 timeTagLength = 8; } else if (aLineLyrics.charAt(6) == ']') {// 长度为5的时间标签格式 timeTagLength = 5; } realLyrcisStartOffset = i; isConfirmTimeTagLeng = true; } for (int j = 0; j < partsLrc.length; j++) { if (partsLrc[j].charAt(0) == '[') {// 确定为时间标签部分,而不是歌词内容部分 String timeTagStr = partsLrc[j].substring(1, timeTagLength + 1); float timeValue = computeTimeTag(timeTagStr); // 添加歌词时间于对应的歌词索引 lrcTimeValAndIndex .add(new LrcTimeAndIndexNum(timeValue, i)); } else // 如果歌词开头的内容也是'['将出现FATAL ERROR {// 确定为歌词内容部分 lyrcisContent.add(partsLrc[j]); } } } } sortByTimeTag(); } /** * @param timeStr * 表示歌词显示时间的String <br> * 例如:<br> * "02:03.30"表示2*60s+3.3s=123.3s=1233ms的时刻 <br> * <b>注意:考虑到歌曲长度不会很长,所以算法中直接取前两位作为分钟, <br> * 而从':'后的单位是秒。但是应为现在lrc文件格式不是很统一,例如: <br> * "02:03"表示2*60s+3s=123s=1230ms,这是短格式表示,也要考虑到。 <br> * 目前,将时间标签分为的两种格式,长度分别为8,5 * @return 计算好的时间值 */ private float computeTimeTag(String timeStr) { int minutes = Integer.parseInt(timeStr.substring(0, 2)); // 取得分钟 float seconds = 0; if (timeStr.length() == 8) { seconds = Float.parseFloat(timeStr.substring(3, 8)); // 取得秒钟 } else if (timeStr.length() == 5) { seconds = Float.parseFloat(timeStr.substring(3, 5)); } return (minutes * 60 + seconds); // 返回时间值 } /** * 更具歌词显示的先后时间顺序进行排序的标准 */ private void sortByTimeTag() { Collections.sort(lrcTimeValAndIndex, new Comparator() { // Parameters: // o1 - the first object to be compared. // o2 - the second object to be compared. // Returns: // a negative integer, zero, or a positive integer as the first // argument is less than, equal to, or greater than the second. // Throws: // ClassCastException - if the arguments' types prevent them from // being compared by this Comparator. public int compare(Object o1, Object o2) { LrcTimeAndIndexNum fl1 = (LrcTimeAndIndexNum) o1; LrcTimeAndIndexNum fl2 = (LrcTimeAndIndexNum) o2; if (fl1.getLrcTime() - fl2.getLrcTime() > 0) { return 1; } else if (fl1.getLrcTime() - fl2.getLrcTime() > 0) { return -1; } else { return 0; } } }); } public void dispalyLrc() { } /** * 主程序入口点 * * @param args * 命令行参数,这里没用到,为<code>null</code> */ public static void main(String[] args) { LrcAnalystCLI lrcAnalyst = new LrcAnalystCLI(); lrcAnalyst.readFile("灰色的心.lrc"); lrcAnalyst.parseLyrics(); Date startTime = new Date(); for (int i = 0; i < lrcTimeValAndIndex.size(); i++) { while (true) { try { Thread.currentThread().sleep(500); Date currentTime = new Date(); LrcTimeAndIndexNum fl = (LrcTimeAndIndexNum) (lrcTimeValAndIndex .get(i)); float diffTime = currentTime.getTime() - startTime.getTime(); if (fl.getLrcTime() - (float) diffTime / 1000 < 0.0) { System.out.println((String) lyrcisContent.get(fl .getLrcIndex() - lrcAnalyst.getRealLrcStartOffset())); break; } } catch (Exception e) { e.printStackTrace(); } } } } } /** * @author daniel */ class LrcTimeAndIndex { /** * 该行歌词的显示时间 */ private float lrcTime; /** * 该行歌词行索引 */ private int lrcIndex; /** * 默认的构造器 */ public LrcTimeAndIndex() { } /** * 带参数的构造器,设置时间标签和对应的歌词行索引 * * @param lrcTime * 时间标签 * @param lrcIndex * 歌词行索引 */ public LrcTimeAndIndex(float lrcTime, int lrcIndex) { this.lrcTime = lrcTime; this.lrcIndex = lrcIndex; } /** * 返回歌词应该出现的时间 * * @return lrcTime 出现时间 * @uml.property name="lrcTime" */ public float getLrcTime() { return lrcTime; } /** * 返回歌词内容行索引 * * @return 歌词内容行索引 * @uml.property name="lrcIndex" */ public int getLrcIndex() { return lrcIndex; } }