Lrc歌词文件格式及其C++代码实现
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Lrc歌词文件格式说明
扩展名为Lrc格式的文件,是MP3播放器唯一能够识别的歌词文件,在MP3数码播放器或千千静听中可以区同步显示歌词。
它是一种包含着“[*:*]”形式的“标签(tag)”的、基于纯文本的歌词专用格式。
最早由郭祥祥先生(Djohan)提出并在其程序中得到应用。
这种歌词文件既可以用来实现卡拉OK 功能(需要专门程序),又能以普通的文字处理软件查看、编辑。
当然,实际操作时通常是用专门的LRC歌词编辑软件进行高效编辑的。
Lrc歌词文本中含有两类标签:
一是标识标签,其格式为“[标识名:值]”主要包含以下预定义的标签:
[ar:歌手名]、[ti:歌曲名]、[al:专辑名]、[by:编辑者(指lrc歌词的制作人)]、[offset:时间补偿值] (其单位是毫秒,正值表示整体提前,负值相反。
这是用于总体调整显示快慢的,但多数的MP3可能不会支持这种标签)。
二是时间标签,形式为“[mm:ss]”或“[mm:ss.fff]”(分钟数:秒数:毫秒数),时间标签需位于某行歌词中的句首部分,一行歌词可以包含多个时间标签(比如歌词中的迭句部分)。
当歌曲播放到达某一时间点时,MP3就会寻找对应的时间标签并显示标签后面的歌词文本,这样就完成了“歌词同步”的功能。
■时间标签(Time-tag)
形式为"[mm:ss]"或"[mm:ss.fff]"(分钟数:秒数)。
数字须为非负整数,比如"[12:34.5]"是有效的,而"[0x0C:-34.5]"无效。
它可以位于某行歌词中的任意位置。
一行歌词可以包含多个时间标签(比如歌词中的迭句部分)。
根据这些时间标签,用户端程序会按顺序依次高亮显示歌词,从而实现卡拉OK 功能。
另外,标签无须排序。
■标识标签(ID-tags)
其格式为"[标识名:值]"。
大小写等价。
以下是预定义的标签。
[ar:艺人名]
[ti:曲名]
[al:专辑名]
[by:编者(指编辑LRC歌词的人)]
[offset:时间补偿值] 其单位是毫秒,正值表示整体提前,负值相反。
这是用于总体调整显示快慢的。
分钟:秒.毫秒]歌词
用记事本按照上述格式写好后,将拓展名改为lrc即可做出该文件.LRC歌词是一种通过编辑
器把歌词按歌曲歌词出现的时间编辑成一个文件,在播放歌曲时同步依次显示出来的一种歌词文件。
把歌曲和LRC歌词命为相同的文件名放在同一目录下,用带显示歌词功能的播放器播放歌曲时歌词就可以同步显示显示,方便查看和学歌。
(供程序员阅读参考)
以下列出了开发支持LRC格式的软件时应遵守的一些标准。
无论是否在行首,行内凡具有“[*:*]”形式的都应认为是标签。
(注意:其中的冒号并非全角字符“:”)
凡是标签都不应显示。
凡是标签,且被冒号分隔的两部分都为非负数,则应认为是时间标签。
因此,对于非标准形式(非“[mm:ss]”)的时间标签也应能识别(如“[0:0]”)。
凡是标签,且非时间标签的,应认为是标识标签。
标识名中大小写等价。
·为了向后兼容,应对未定义的新标签作忽略处理。
另应对注释标签([:])后的同一行内容作忽略处理。
应允许一行中存在多个标签,并能正确处理。
应能正确处理未排序的标签。
以下附上C++实现代码,支持ANSI和Unicode格式,超强纠错。
-----------------------------------------------------Lyric.h----------------------------------------------------- typedef struct
{
DWORD dwStartTime; // The unit is millisecond
CString csLyric;
} LineLyric, *PLineLyric;
class CLyric
{
public:
CLyric();
~CLyric();
CString m_csArtist;
CString m_csTitle;
CString m_csAlbum;
CString m_csBy;
LONG m_lOffset; // Lead time of the lyric
CString m_csKey;
public:
BOOL Load(LPCWSTR szFileName, LONG lUserOffset = 0); // If lUserOffset > 0: ahead; if < 0: put off
void UnLoad();
BOOL GetItemByID(DWORD dwItemID, LineLyric &lyric); // Get the lyric data by dwItemID
DWORD GetTimeByID(DWORD dwItemID); // Get the lyric's start time by dwItemID
LONG GetItemIDByTime(DWORD dwTime, DWORD dwStartLineID = 0);// Search the line index by time(in milliseconds) begin at nStartLineID
DWORD GetTotalNum() { return m_dwLineNum; }
BOOL IsValid() { return m_bValid; }
private:
BOOL m_bValid;
CArray<LineLyric> m_LyricList;
DWORD m_dwLineNum;
BOOL m_bAnalyseTag;
LONG m_lUserOffset;
__forceinline BOOL ReadALine(FILE *fp, CHAR *buff, INT bufflen, INT &readlen);
__forceinline void AnalyseLine(CHAR *szLine, INT nLength);
__forceinline BOOL CheckTag(CHAR *szText);
__forceinline LONG GetStartTime(CHAR *szTime);
__forceinline BOOL ReadALineW(FILE *fp, WCHAR *buff, INT bufflen, INT
&readlen);
__forceinline void AnalyseLineW(WCHAR *szLine, INT nLength);
__forceinline BOOL CheckTagW(WCHAR *szText);
__forceinline LONG GetStartTimeW(WCHAR *szTime);
__forceinline DWORD Find(DWORD dwTime); // return the line ID matched the time
};
-----------------------------------------------------Lyric.cpp----------------------------------------------------- #include"Lyric.h"
#define MAX_LYRIC_LINE_LEN512
#define MAX_LYRIC_LEN128
#define MAX_LYRIC_LINE_NUM5000
#define MAX_LYRIC_TIME_LEN30
CLyric::CLyric()
{
m_bValid = FALSE;
m_lUserOffset = 0;
}
CLyric::~CLyric()
{
}
BOOL CLyric::Load(LPCWSTR szFileName, LONG lUserOffset)
{
UnLoad();
FILE*fp;
INT readlen;
m_lUserOffset = lUserOffset;
fp = _tfopen(szFileName, _T("r"));
if (fp == NULL)
{
return FALSE;
}
WORD wUnicode_tag = 0;
DWORD dwReadLen = 0;
try
{
dwReadLen = fread((void *)&wUnicode_tag, sizeof(WCHAR), 1, fp);
}
catch (...)
{
printf("CLyric::Load: fread Exception: %s!\r\n", szFileName);
fclose(fp);
return FALSE;
}
if (dwReadLen != 1)
{
printf("CLyric::Load: read file %s failed!\r\n", szFileName);
fclose(fp);
return FALSE;
}
if (wUnicode_tag == 0xfeff) // unicode file
{
fclose(fp);
fp = _tfopen(szFileName, _T("rb"));
if (fp == NULL)
{
return FALSE;
}
fseek(fp, sizeof(WCHAR), SEEK_SET);
WCHAR wstr[MAX_LYRIC_LINE_LEN];
do
{
if (!ReadALineW(fp, wstr, MAX_LYRIC_LINE_LEN, readlen)) break;
if (readlen <= 0)
continue;
AnalyseLineW(wstr, readlen);
}
while (m_dwLineNum < MAX_LYRIC_LINE_NUM);
}
else
{
fseek(fp, 0L, SEEK_SET);
CHAR str[MAX_LYRIC_LINE_LEN];
do
{
if (!ReadALine(fp, str, MAX_LYRIC_LINE_LEN, readlen)) break;
if (readlen <= 0)
continue;
AnalyseLine(str, readlen);
}
while (m_dwLineNum < MAX_LYRIC_LINE_NUM);
}
fclose(fp);
m_bValid = TRUE;
return TRUE;
}
void CLyric::UnLoad()
{
m_bValid = FALSE;
m_csArtist.Empty();
m_csTitle.Empty();
m_csAlbum.Empty();
m_csBy.Empty();
m_lOffset = 0;
m_csKey.Empty();
m_LyricList.RemoveAll();
m_dwLineNum = 0;
m_bAnalyseTag = TRUE;
m_lUserOffset = 0;
}
BOOL CLyric::GetItemByID(DWORD dwItemID, LineLyric &lyric) {
if (dwItemID < (DWORD)m_LyricList.GetSize())
{
lyric = m_LyricList[dwItemID];
return TRUE;
}
else
{
return FALSE;
}
}
DWORD CLyric::GetTimeByID(DWORD dwItemID)
{
DWORD dwTotalItem = (DWORD)m_LyricList.GetSize();
if (dwTotalItem == 0)
return 0;
if (dwItemID < dwTotalItem)
{
return m_LyricList[dwItemID].dwStartTime;
}
else
{
return m_LyricList[dwTotalItem-1].dwStartTime;
}
}
LONG CLyric::GetItemIDByTime(DWORD dwTime, DWORD dwStartLineID)
{
DWORD dwLineNum = m_LyricList.GetSize();
if (dwTime <= 0 || dwLineNum == 0)
return -1;
for (DWORD i = dwStartLineID; i < dwLineNum; i++)
{
if (m_LyricList[i].dwStartTime > dwTime)
{
return i-1;
}
}
return dwLineNum-1;
}
__forceinline BOOL CLyric::ReadALine(FILE *fp, CHAR *buff, INT bufflen, INT &readlen) {
readlen = 0;
if (feof(fp)) /* End or file, return -1 */
return FALSE;
int ch;
do
{
ch = fgetc(fp);
if (ch == '\r' || ch == '\n' || ch == EOF)
break;
if (readlen >= bufflen-1)
continue; // Use continue for ignoring the remanent character of the line
*buff = ch;
buff++;
readlen++;
} while (!feof(fp));
if (readlen > 0 && *(buff-1) == '\r')
*(buff-1) = 0;
else
*buff = 0;
return TRUE;
}
__forceinline void CLyric::AnalyseLine(CHAR *szLine, INT nLength)
{
WCHAR szLyric[MAX_LYRIC_LEN];
CHAR szTime[MAX_LYRIC_TIME_LEN];
CHAR *szTimeBegin = NULL; // Begin at the next char of '['
CHAR *szTimeEnd = NULL; // End at ']'
CHAR *szNextTimeBegin = strchr(szLine, '[');
if (szNextTimeBegin == NULL)
return;
szNextTimeBegin++;
CHAR *szNextTimeEnd = strchr(szNextTimeBegin, ']');
if (szNextTimeEnd == NULL)
return;
LineLyric lyric;
LONG lStartTime;
while (szNextTimeBegin != NULL && szNextTimeEnd != NULL)// Support multi time/tag in one line
{
szTimeBegin = szNextTimeBegin;
szTimeEnd = szNextTimeEnd;
szNextTimeBegin = strchr(szTimeEnd, '[');
if (szNextTimeBegin != NULL)
{
szNextTimeEnd = strchr(szNextTimeBegin, ']');
if (szNextTimeEnd != NULL)
{
// Find a next Time in the same line
*szNextTimeBegin = 0; // Set '[' as the end flag of the string
szNextTimeBegin++;
}
}
if (szTimeEnd > szTimeBegin+1)
{
INT nTimeLen = min(MAX_LYRIC_TIME_LEN-1,
szTimeEnd-szTimeBegin);
strncpy(szTime, szTimeBegin, nTimeLen);
szTime[nTimeLen] = 0;
if (m_bAnalyseTag)
{
if (CheckTag(szTime))
continue;
}
lStartTime = GetStartTime(szTime);
if (lStartTime < 0)
continue;
lyric.dwStartTime = max((lStartTime - m_lOffset - m_lUserOffset), 0);
MultiByteToWideChar(Core_GetDefCodePage(), 0, szTimeEnd+1, -1, szLyric, MAX_LYRIC_LEN-1);
lyric.csLyric = szLyric;
if (lyric.csLyric.IsEmpty() && szTimeEnd+2 != 0)
{
// To get MultiTimeLyric
CHAR *szMultiTimeLyric = szTimeEnd+2;
szMultiTimeLyric = strchr(szMultiTimeLyric, ']');
if (szMultiTimeLyric != NULL)
{
CHAR *szMultiTimeLyricBegin = szMultiTimeLyric+1;
while (1)
{
szMultiTimeLyric = strchr(szMultiTimeLyricBegin, '[');
if (szMultiTimeLyric == NULL) // Find MultiTimeLyric, it's at the end of the line
{
MultiByteToWideChar(Core_GetDefCodePage(), 0, szMultiTimeLyricBegin, -1, szLyric, MAX_LYRIC_LEN-1);
lyric.csLyric = szLyric;
break;
}
else if (szMultiTimeLyric-szMultiTimeLyricBegin > 0) // Find MultiTimeLyric, it's at the middle of the line
{
MultiByteToWideChar(Core_GetDefCodePage(), 0, szMultiTimeLyricBegin, -1, szLyric, MAX_LYRIC_LEN-1);
lyric.csLyric = szLyric;
lyric.csLyric = lyric.csLyric.Mid(0,
szMultiTimeLyric-szMultiTimeLyricBegin);
break;
}
szMultiTimeLyric = strchr(szMultiTimeLyric+1, ']');
if (szMultiTimeLyric == NULL) // Invalid format, has '[', but no ']'
{
MultiByteToWideChar(Core_GetDefCodePage(), 0, szMultiTimeLyricBegin, -1, szLyric, MAX_LYRIC_LEN-1);
lyric.csLyric = szLyric;
break;
}
szMultiTimeLyricBegin = szMultiTimeLyric+1;
}
}
}
if (m_dwLineNum > 0 || !lyric.csLyric.IsEmpty()) // First line must no be empty
{
if (m_dwLineNum > 0 && lyric.dwStartTime >=
m_LyricList.GetAt(m_dwLineNum-1).dwStartTime)
m_LyricList.Add(lyric);
else
m_LyricList.InsertAt(Find(lyric.dwStartTime), lyric); // Auto sort by time
m_dwLineNum++;
}
m_bAnalyseTag = FALSE; // Consider Tag info is at the beginning of the file
}
}
}
BOOL CLyric::CheckTag(CHAR *szText)
{
if (_strnicmp(szText, "ar", 2) == 0)
{
m_csArtist = szText+3;
return TRUE;
}
if (_strnicmp(szText, "ti", 2) == 0)
{
m_csTitle = szText+3;
return TRUE;
}
if (_strnicmp(szText, "al", 2) == 0)
{
m_csAlbum = szText+3;
return TRUE;
}
if (_strnicmp(szText, "by", 2) == 0)
{
m_csBy = szText+3;
return TRUE;
}
if (_strnicmp(szText, "offset", 6) == 0)
{
m_lOffset = atol(szText+7);
return TRUE;
}
if (_strnicmp(szText, "key", 3) == 0)
{
m_csKey = szText+4;
return TRUE;
}
return FALSE;
}
__forceinline LONG CLyric::GetStartTime(CHAR *szTime)
{
INT nMinute = 0;
INT nSecond = 0;
INT nMilliSecond = 0;
CHAR *szMinuteEnd = strrchr(szTime, ':');
if (szMinuteEnd == NULL)
{
nMinute = 0;
szMinuteEnd = szTime;
}
else
{
CHAR szMinute[10];
INT nMinuteLen = min(9, szMinuteEnd-szTime);
strncpy(szMinute, szTime, nMinuteLen);
if (nMinuteLen > 0 && (szMinute[0] < '0' || szMinute[0] > '9')) return -1;
nMinute = atoi(szMinute);
if (nMinute < 0)
nMinute = 0;
szMinuteEnd++;
}
CHAR *szSecondEnd = strrchr(szMinuteEnd, '.');
if (szSecondEnd == NULL)
{
nSecond = atoi(szMinuteEnd);
if (nSecond < 0)
nSecond = 0;
}
else
{
CHAR szSecond[10];
INT nSecondLen = min(9, szSecondEnd-szMinuteEnd);
strncpy(szSecond, szMinuteEnd, nSecondLen);
if (nSecondLen > 0 && (szSecond[0] < '0' || szSecond[0] > '9'))
return -1;
nSecond = atoi(szSecond);
if (nSecond < 0)
nSecond = 0;
szSecondEnd++;
INT nMilliSecondLen = strlen(szSecondEnd);
if (nMilliSecondLen == 1)
nMilliSecond = atoi(szSecondEnd) * 100;
else if (nMilliSecondLen == 2)
nMilliSecond = atoi(szSecondEnd) * 10;
else if (nMilliSecondLen == 3)
nMilliSecond = atoi(szSecondEnd);
if (nMilliSecond < 0)
nMilliSecond = 0;
}
return nMinute*60000+nSecond*1000+nMilliSecond;
}
__forceinline BOOL CLyric::ReadALineW(FILE *fp, WCHAR *buff, INT bufflen, INT &readlen)
{
WCHAR *pReturn = fgetws(buff, bufflen, fp);
if (pReturn == NULL)
return FALSE;
readlen = lstrlen(buff);
if (readlen > 0 && buff[readlen-1] == _T('\n'))
{
buff[readlen-1] = 0;
readlen--;
}
if (readlen > 0 && buff[readlen-1] == _T('\r'))
{
buff[readlen-1] = 0;
readlen--;
}
return TRUE;
}
__forceinline void CLyric::AnalyseLineW(WCHAR *szLine, INT nLength)
{
WCHAR szTime[MAX_LYRIC_TIME_LEN];
WCHAR *szTimeBegin = NULL; // Begin at the next char of '['
WCHAR *szTimeEnd = NULL; // End at ']'
WCHAR *szNextTimeBegin = wcschr(szLine, _T('['));
if (szNextTimeBegin == NULL)
return;
szNextTimeBegin++;
WCHAR *szNextTimeEnd = wcschr(szNextTimeBegin, _T(']'));
if (szNextTimeEnd == NULL)
return;
LineLyric lyric;
LONG lStartTime;
while (szNextTimeBegin != NULL && szNextTimeEnd != NULL)// Support multi time/tag in one line
{
szTimeBegin = szNextTimeBegin;
szTimeEnd = szNextTimeEnd;
szNextTimeBegin = wcschr(szTimeEnd, _T('['));
if (szNextTimeBegin != NULL)
{
szNextTimeEnd = wcschr(szNextTimeBegin, _T(']'));
if (szNextTimeEnd != NULL)
{
// Find a next Time in the same line
*szNextTimeBegin = 0; // Set '[' as the end flag of the string
szNextTimeBegin++;
}
}
if (szTimeEnd > szTimeBegin+1)
{
INT nTimeLen = min(MAX_LYRIC_TIME_LEN-1,
szTimeEnd-szTimeBegin);
wcsncpy(szTime, szTimeBegin, nTimeLen);
szTime[nTimeLen] = 0;
if (m_bAnalyseTag)
{
if (CheckTagW(szTime))
continue;
}
lStartTime = GetStartTimeW(szTime);
if (lStartTime < 0)
continue;
lyric.dwStartTime = max((lStartTime - m_lOffset - m_lUserOffset), 0);
lyric.csLyric = szTimeEnd+1;
if (lyric.csLyric.IsEmpty() && szTimeEnd+2 != 0)
{
// To get MultiTimeLyric
WCHAR *szMultiTimeLyric = szTimeEnd+2;
szMultiTimeLyric = wcschr(szMultiTimeLyric, _T(']'));
if (szMultiTimeLyric != NULL)
{
WCHAR *szMultiTimeLyricBegin = szMultiTimeLyric+1;
while (1)
{
szMultiTimeLyric = wcschr(szMultiTimeLyricBegin, _T('['));
if (szMultiTimeLyric == NULL) // Find MultiTimeLyric, it's at the end of the line
{
lyric.csLyric = szMultiTimeLyricBegin;
break;
}
else if (szMultiTimeLyric-szMultiTimeLyricBegin > 0) // Find MultiTimeLyric, it's at the middle of the line
{
lyric.csLyric = szMultiTimeLyricBegin;
lyric.csLyric = lyric.csLyric.Mid(0,
szMultiTimeLyric-szMultiTimeLyricBegin);
break;
}
szMultiTimeLyric = wcschr(szMultiTimeLyric+1, _T(']'));
if (szMultiTimeLyric == NULL) // Invalid format, has '[', but no ']'
{
lyric.csLyric = szMultiTimeLyricBegin;
break;
}
szMultiTimeLyricBegin = szMultiTimeLyric+1;
}
}
}
if (m_dwLineNum > 0 || !lyric.csLyric.IsEmpty()) // First line must no be empty
{
if (m_dwLineNum > 0 && lyric.dwStartTime >=
m_LyricList.GetAt(m_dwLineNum-1).dwStartTime)
m_LyricList.Add(lyric);
else
m_LyricList.InsertAt(Find(lyric.dwStartTime), lyric); // Auto sort by time
m_dwLineNum++;
}
m_bAnalyseTag = FALSE; // Consider Tag info is at the beginning of the file
}
}
}
BOOL CLyric::CheckTagW(WCHAR *szText)
{
if (_wcsnicmp(szText, _T("ar"), 2) == 0)
{
m_csArtist = szText+3;
return TRUE;
}
if (_wcsnicmp(szText, _T("ti"), 2) == 0)
{
m_csTitle = szText+3;
return TRUE;
}
if (_wcsnicmp(szText, _T("al"), 2) == 0)
{
m_csAlbum = szText+3;
return TRUE;
}
if (_wcsnicmp(szText, _T("by"), 2) == 0)
{
m_csBy = szText+3;
return TRUE;
}
if (_wcsnicmp(szText, _T("offset"), 6) == 0)
{
m_lOffset = _wtol(szText+7);
return TRUE;
}
if (_wcsnicmp(szText, _T("key"), 3) == 0)
{
m_csKey = szText+4;
return TRUE;
}
return FALSE;
}
__forceinline LONG CLyric::GetStartTimeW(WCHAR *szTime)
{
INT nMinute = 0;
INT nSecond = 0;
INT nMilliSecond = 0;
WCHAR *szMinuteEnd = wcsrchr(szTime, ':');
if (szMinuteEnd == NULL)
{
nMinute = 0;
szMinuteEnd = szTime;
}
else
{
WCHAR szMinute[10];
INT nMinuteLen = min(9, szMinuteEnd-szTime);
wcsncpy(szMinute, szTime, nMinuteLen);
if (nMinuteLen > 0 && (szMinute[0] < _T('0') || szMinute[0] > _T('9'))) return -1;
nMinute = _wtoi(szMinute);
if (nMinute < 0)
nMinute = 0;
szMinuteEnd++;
}
WCHAR *szSecondEnd = wcsrchr(szMinuteEnd, _T('.'));
if (szSecondEnd == NULL)
{
nSecond = _wtoi(szMinuteEnd);
if (nSecond < 0)
nSecond = 0;
}
else
{
WCHAR szSecond[10];
INT nSecondLen = min(9, szSecondEnd-szMinuteEnd);
wcsncpy(szSecond, szMinuteEnd, nSecondLen);
if (nSecondLen > 0 && (szSecond[0] < _T('0') || szSecond[0] > _T('9'))) return -1;
nSecond = _wtoi(szSecond);
if (nSecond < 0)
nSecond = 0;
szSecondEnd++;
INT nMilliSecondLen = lstrlen(szSecondEnd);
if (nMilliSecondLen == 1)
nMilliSecond = _wtoi(szSecondEnd) * 100;
else if (nMilliSecondLen == 2)
nMilliSecond = _wtoi(szSecondEnd) * 10;
else if (nMilliSecondLen == 3)
nMilliSecond = _wtoi(szSecondEnd);
if (nMilliSecond < 0)
nMilliSecond = 0;
}
return nMinute*60000+nSecond*1000+nMilliSecond;
}
__forceinline DWORD CLyric::Find(DWORD dwTime)
{
DWORD i = 0;
for (i = 0; i < (DWORD)m_LyricList.GetSize(); i++)
{
if (m_LyricList[i].dwStartTime > dwTime)
{
break;
}
}
return i; }。