音乐歌词同步3. LRC
• LRC是英文lyric(歌词)的缩写,被用做歌
词文件的扩展名。以lrc为扩展名的歌词文
件可以在各类数码播放器中同步显示。LRC
歌词是一种包含着“*:*”形式的“标签(tag)”的、
基于纯文本的歌词专用格式。最早由郭祥
祥先生(Djohan)提出并在其程序中得到应用。
7. 解析歌词
• 歌名
• 艺术家
• 专辑
• 歌词制作人
• 时间偏移量
• 时间歌词列表
– {开始时间1,结束时间2,句子1}
– {开始时间1,结束时间2,句子2}
– …
– {开始时间n,结束时间n,句子n}
9. 开发标准
• 无论是否在行首,行内凡具有[*:*]形式的都应认为是标签。
• 凡是标签都不应显示。
• 凡是标签,且被冒号分隔的两部分都为非负数,则应认为是时
间标签。
• 因此,对于非标准形式(非[mm:ss]或[mm:ss.ff])的时间标签也
应能识别(如“*m:s+”)。
• 应能正确识别连续的时间标签.如[00:01.555][01:03.234]歌词内
容。
• 凡是标签,且非时间标签的,应认为是标识标签。
• 标识名中大小写等价。
• 为了向后兼容,应对未定义的新标签作忽略处理。另应对注释
标签([:])后的同一行内容作忽略处理。
• 应允许一行中存在多个标签,并能正确处理。
• 应能正确处理未排序的标签。
11. private void parseIdTags(String lyricStr) {
Pattern pattern = Pattern.compile("[(ti|ar|al|by|offset):([^]]*)]",
Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(lyricStr);
while (matcher.find()) {
String tag = matcher.group(1);
String value = matcher.group(2);
if ("ti".equalsIgnoreCase(tag))
ti = value;
else if ("ar".equalsIgnoreCase(tag))
ar = value;
else if ("al".equalsIgnoreCase(tag))
al = value;
else if ("by".equalsIgnoreCase(tag))
by = value;
else if ("offset".equalsIgnoreCase(tag))
offset = Long.parseLong(value);
}
}
12. 解析时间标签
[03:01.92][02:25.63][00:56.90]天正在等烟雨[00:50.77]去到
我去不了的地方
…
Regex: ((?:[d{1,2}:d{1,2}(?:.d{1,3}){0,1}])+)([^[]*)
一句时间歌词格式.也即包括时间戳标签以及该时间所对应
的歌词.表达式中的"+"号表示允许同时有多个连续的时
间戳标签存在(注意:多个时间标签之间不能有空格.否则
空格将也会被认为是歌词),这也正是符合上面提到的开
发标准中的第5条.那么,如何描述歌词呢? 一句歌词就是
从时间标签结束以后一直到下一个"["出现之前,就为一
句歌词的内容了.所以用一个非左括号表达式来匹配歌词
内容.
14. • Regex:
[((d{1,2}):(d{1,2})((?:.d{1,3}){0,1}))]
15. private void parseTimeTags(String lyricStr) {
Pattern timePattern = Pattern.compile("[((d{1,2}):(d{1,2})((?:.d{1,3}){0,1}))]");
Pattern timeTagPattern = Pattern.compile("((?:[d{1,2}:d{1,2}(?:.d{1,3}){0,1}])+)([^[]*)");
Matcher timeTagMatcher = timeTagPattern.matcher(lyricStr);
while (timeTagMatcher.find()) {
String time = timeTagMatcher.group(1); // 时间部分
String text = timeTagMatcher.group(2).trim(); // 歌词部分
Matcher timeMatcher = timePattern.matcher(time);
while (timeMatcher.find()) {
long minute = Integer.parseInt(timeMatcher.group(2));
long second = Integer.parseInt(timeMatcher.group(3));
long from = (minute * 60 + second) * 1000L; // 转换成毫秒数
if (timeMatcher.groupCount() == 4) // 如果还有百分之一秒部分,则加上
from += Integer.parseInt(timeMatcher.group(2)) * 10;
if (offset != 0) // 处理偏移量
from += offset;
Sentence sentence = new Sentence(timeMatcher.group(1), from, text);
sentences.add(sentence);
}
}
Collections.sort(sentences); // 按时间排序
}
17. LyricView
public class LyricView extends View {
private Lyric mLyric;
private Sentence mSentence;
public void update(long time) {
mSentence = mLyric.getSentence(time);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, "in onDraw");
if (mLyricLoaded) {
drawFocusedLine(canvas);
drawOtherLines(canvas);
} else {
drawErrorMessage(canvas);
}
}
private void drawFocusedLine(Canvas canvas) {...}
private void drawOtherLines(Canvas canvas) {...}
}