Lrc歌词解析器0.0.2.0
时间:2010-08-12 来源:mysiyer
1. 类图
2. 源码清单
/**
* 文件名:LrcAnalyst.java
* 环境: GNU/Linux Ubuntu 7.04 + Eclipse 3.2 + JDK 1.6
* 功能:解析Lrc歌词文件的主驱动程序
* 版本:0.0.2.0
* 作者:88250
* 日期:2007.4.27
* E-mail & MDN: [email protected]
* QQ:845765
*/
import javax.swing.JFrame;
/**
* 测试歌词解析结果
*/
public class LrcAnalyst
{
public LrcAnalyst()
{
JFrame frame = new JFrame("Lrc Srcoll");
frame.setBounds(600, 300, 400, 200);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
LrcScrollPane pane = new LrcScrollPane();
frame.getContentPane().add(pane);
frame.setVisible(true);
pane.scroll();
}
/**
* 主程序入口点
*
* @param args
* 命令行参数,这里没用到,为<code>null</code>
*/
public static void main(String[] args)
{
new LrcAnalyst();
}
}
/**
* 文件名:LrcAnalystBase.java
* 环境: GNU/Linux Ubuntu 7.04 + Eclipse 3.2 + JDK 1.6
* 功能:解析Lrc歌词文件
* 版本:0.0.2.0
* 作者:88250
* 日期:2007.4.22
* E-mail & MDN: [email protected]
* QQ:845765
*/
import java.io.*;
import java.util.*;
/**
* 歌词解释类,用于对lrc歌词文件的解析
*/
public class LrcAnalystBase
{
/**
* lrc文件缓冲区
*/
private static Vector lyrics;
/**
* 格式化好的歌词时间与索引,格式参考TimeAndIndex类
*/
private static Vector lrcTimeValAndIndex;
/**
* 返回保存歌词时间与对应歌词的索引容器
* @return LrcTimeAndIndex 时间标签与歌词索引
*/
public Vector getLrcTimeValAndIndex()
{
return lrcTimeValAndIndex;
}
/**
* 歌词的实际内容
*/
private static Vector lyrcisContent;
/**
* 返回歌词实际内容的容器
* @return 歌词实际内容lyrcisContent
*/
public Vector getLrcContent()
{
return 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 LrcAnalystBase()
{
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(" ");
for (int i = 0; i < lyrcisText.length; i++)
{
lyrics.add(lyrcisText[i]);
}
}
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[] <br>
* 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 LrcTimeAndIndex(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)
{
LrcTimeAndIndex fl1 = (LrcTimeAndIndex) o1;
LrcTimeAndIndex fl2 = (LrcTimeAndIndex) o2;
if (fl1.getLrcTime() - fl2.getLrcTime() > 0)
{
return 1;
}
else if (fl1.getLrcTime() - fl2.getLrcTime() > 0)
{
return -1;
}
else
{
return 0;
}
}
});
}
}
/**
* 用于保存歌词时间与歌词容器的下标索引
*/
class LrcTimeAndIndex
{
/**
* 该行歌词的显示时间
* @uml.property name="lrcTime"
*/
private float lrcTime;
/**
* 该行歌词行索引
* @uml.property name="lrcIndex"
*/
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;
}
}
/**
* 文件名:LrcScrollPane.java
* 环境: GNU/Linux Ubuntu 7.04 + Eclipse 3.2 + JDK 1.6
* 功能:滚动的Lrc歌词显示面板
* 版本:0.0.2.0
* 作者:88250
* 日期:2007.4.28
* E-mail & MDN: [email protected]
* QQ:845765
*/
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import javax.swing.JPanel;
import java.util.Date;
public class LrcScrollPane extends JPanel
{
private final int heightOfChar = 20;
private final Font fontOfChar = new Font("monospaced", Font.BOLD, 14);
private Point scrollPoint; // 参考点,会慢慢向上移。
private LrcAnalystBase lrcAnalyst;
private static int lrcIndex = 0;
public LrcScrollPane()
{
lrcAnalyst = new LrcAnalystBase();
lrcAnalyst.readFile("六月的雨.lrc");
lrcAnalyst.parseLyrics();
scrollPoint = new Point(5, getSize().height);
}
/**
* 实现歌词的滚动显示
*/
public void scroll()
{
Date startTime = new Date();
for (int i = 0; i < lrcAnalyst.getLrcTimeValAndIndex().size(); i++)
{
while (true)
{
try
{
Thread.currentThread().sleep(50);
Date currentTime = new Date();
LrcTimeAndIndex fl = (LrcTimeAndIndex) (lrcAnalyst
.getLrcTimeValAndIndex().get(i));
float diffTime = currentTime.getTime()
- startTime.getTime();
lrcIndex = fl.getLrcIndex()- lrcAnalyst.getRealLrcStartOffset();
if (fl.getLrcTime() - (float) diffTime / 1000 < 0.0)
{
scrollPoint.y--;
//System.out.println((String) lrcAnalyst.getLrcContent().get(
// fl.getLrcIndex()- lrcAnalyst.getRealLrcStartOffset()));
repaint();
break;
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
public void paintComponent(Graphics brush)
{
// 用背景色覆盖,原来的文字就看不到了。再写上新的文字,由于视觉停留,
// 看起来文字就会滚动
brush.setColor(getBackground());
brush.fillRect(0, 0, getSize().width, getSize().height);
brush.setColor(Color.blue);
brush.setFont(fontOfChar);
// i为下标,用来历遍歌词缓存容器,
// 当scrollPoint.y+(heightOfChar*i)<0 时,会把文字写到面板上方,看不见。
// 没有必要显示。
// scrollPoint.y+(heightOfChar*i)=0 => i=(-scrollPoint.y/heightOfChar)
// 故要使(-scrollPoint.y/heightOfChar)和 0 作比较,要是小于0,i取0
/*int i = 0;
if ((-scrollPoint.y / heightOfChar) > 0)
{
i = (-scrollPoint.y / heightOfChar);
}*/
/*String lyrics = null;
for (; i < lrcAnalyst.getLrcContent().size(); i++)
{
// 当条件成立,文字会写到面板下方,也没有必要显示,跳出循环
if (scrollPoint.y + (heightOfChar * i) > getSize().height)
{
break;
}
lyrics = (String) (lrcAnalyst.getLrcContent().get(i));
// 见scroll(),靠scrollPoint.y的上移,使文字上移
brush.drawString(lyrics, scrollPoint.x, scrollPoint.y
+ (heightOfChar * i));
} */
String lyrics = (String) lrcAnalyst.getLrcContent().get(lrcIndex);
// 见scroll(),靠scrollPoint.y的上移,使文字上移
brush.drawString(lyrics, scrollPoint.x, -scrollPoint.y + 20);
}
}
3. 工程文件
LrcAnalyst0.0.2.0 by eclipse 3.2.0
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/DL88250/archive/2007/04/29/1591539.aspx