C++读取歌词(lrc)文件,分享歌词时间标签和歌词文本的方法

编程语言 来源:zy380526481 26℃ 0评论
本人最近在写一个音乐播放器,做了一个显示歌词的功能。虽然很多已经有很多人有自己的办法,在这里我还是想介绍一下我自己的方法。
读取歌词文件并不困难,因为lrc格式的歌词本身很有规律,下面为一个lrc文件的一部分:
[ti:なわとび]
[ar:小泉花陽(CV.久保ユリカ)]
[al:「ラブライブ!」オリジナルソング CD3]
[by:萊特] 
[00:00.42]なわとび
[00:04.29]TVアニメ「ラブライブ!」オリジナルソング CD3
[00:06.40]作詞:畑亜貴
[00:08.43]作曲:rino
[00:10.40]編曲:藤田宜久
[00:12.39]歌:小泉花陽(CV.久保ユリカ)
[00:15.91]
[00:27.66]出会いがわたしを変えたみたい
[00:33.11]なりたい自分をみつけたの
[00:38.82]ずっとずっとあこがれを
[00:45.74]胸の中だけで育ててた
  歌词文件中的每一句由中括号组成的时间标签和歌词文本组成,其中时间标签分别用冒号和圆点分隔了分钟数、秒钟数和百分之一秒数。根据这个规律,就可以得出读取歌词文件的方法:
  1. 逐个字符读取文件,并将读取到的字符压入到一个string对象中。
  2. 直到读取到'['时,一句歌词便读取完毕,将该string对象压入到一个string容器中。
  3. 到达文件尾时,文件中的每一句歌词(包括时间标签)已经保存到string容器中。
  4. 依次处理刚刚得到的string容器中的每一个字符串。
  5. 查找字符串中的冒号":",将冒号之前的字符作为分钟数。
  6. 查找字符串中的圆点".",将圆点前面两个字符作为秒钟数,圆点后面两个字符作为百分之一秒数。
  7. 查找字符串中的"]",将后面的字符作为歌词文本。
我写了一个歌词Lrycs类,下面是源代码:
#pragma once
#include
#include
#include
#include
#include"Common.h"
using std::ofstream;
using std::ifstream;
using std::string;
using std::vector;

struct Lyric
{
	Time time;
	string text;
};

class CLyrics
{
private:
	vector m_lyrics;		//储存每一句歌词(包含时间标签和文本)
	vector m_lyrics_str;		//储存未拆分时间标签的每一句歌词

public:
	CLyrics(string file_name);		//构造函数获取歌词文件中的每一句歌词,并保存在m_lyrics_str中
	CLyrics(){}
	void DisposeLyric();		//将每一句歌词中的时间和文本分开,并保存在m_lyrics中
	bool IsEmpty() const;		//判断是否有歌词
	string GetLyric(Time time);		//根据时间返回一句歌词
};

//在构造函数中将歌词文件的每一句分开,并储存在m_lyric_str中
CLyrics::CLyrics(string file_name)
{
	ifstream OpenFile{ file_name };
	char ch;
	string temp;
	OpenFile.get(ch);		//读取第一个字符
	if(ch != '[') temp.push_back(ch);		//如果是'['就跳过它
	while (!OpenFile.eof())
	{
		do
		{
			OpenFile.get(ch);
			temp.push_back(ch);
		} while (ch != '[' && !OpenFile.eof());		//不断读取一个字符,直到读到"["为止("["每一句歌词以[开始)
		m_lyrics_str.push_back(temp);		//将读取到的一句歌词保存到m_lyrics_str中
		temp.clear();
	}
	//注:此函数执行完后,字符串的前面没有'[',所有的'['都会出现在字符串的末尾
}

void CLyrics::DisposeLyric()
{
	int index;
	string temp;
	Lyric lyric;
	for (int i{ 0 }; i < m_lyrics_str.size(); i++)
	{
		if (m_lyrics_str[i][0]>'9' || m_lyrics_str[i][0] < '0')		//如果该句歌词的时间标签的第1个字符不是数字,就跳过这句歌词
			continue;
		index = m_lyrics_str[i].find_first_of(':');		//查找第1个冒号(冒号前面的为分钟数)
		if (index == string::npos) continue;
		temp = m_lyrics_str[i].substr(0, index);		//获取时间标签的分钟数
		lyric.time.min = atoi(temp.c_str());
		index = m_lyrics_str[i].find_first_of('.', index);
		if (index == string::npos) continue;
		temp = m_lyrics_str[i].substr(index - 2, 2);		//获取时间标签的秒钟数(点号前面两个字符为秒钟数)
		lyric.time.sec = atoi(temp.c_str());
		temp = m_lyrics_str[i].substr(index + 1, 2);		//获取时间标签的百分之一秒数(点号后面的两个字符)
		lyric.time.msec = atoi(temp.c_str()) * 10;

		index = m_lyrics_str[i].find_first_of(']', index);		//查找第1个],“]”号后面的为歌词文本
		if (index == string::npos) continue;
		lyric.text = m_lyrics_str[i].substr(index + 1, m_lyrics_str[i].size() - index - 1);
		if (lyric.text[lyric.text.size() - 1] == '[') lyric.text.resize(lyric.text.size() - 1);		//删除歌词文本后面的'['
		if (lyric.text[lyric.text.size() - 1] == '\n') lyric.text.resize(lyric.text.size() - 1);		//删除歌词文文本后面的换行符

		if (lyric.text.empty())		//如果时间标签后没有文本,显示为“……”
			lyric.text = "……";
		m_lyrics.push_back(lyric);
	}
	if (!m_lyrics.empty())
	{
		size_t size{ m_lyrics[m_lyrics.size() - 1].text.size() };
		m_lyrics[m_lyrics.size() - 1].text[size - 1] = '\0';		//将最后一句歌词的最后一个字符替换成'\0'
	}
}

bool CLyrics::IsEmpty() const
{
	return (m_lyrics.size() == 0);
}

string CLyrics::GetLyric(Time time)
{
	for (int i{ 0 }; i < m_lyrics.size(); i++)
	{
		if (m_lyrics[i].time>time)		//如果找到第一个时间标签比要显示的时间大,返回该时间标签的前一句歌词
		{
			return m_lyrics[i == 0 ? i : i - 1].text;
		}
	}
	return m_lyrics[m_lyrics.size() - 1].text;		//如果没有时间标签比要显示的时间大,就返回最后一句歌词
}
 
其中定义了一个Lyric结构体,用于保存一句歌词,其中包含了时间标签(Time类型)和歌词文本(string类型)。Time的定义如下:
struct Time
{
	int min;
	int sec;
	int msec;
};

bool operator>(Time time1, Time time2)
{
	if (time1.min != time2.min)
		return (time1.min > time2.min);
	else if (time1.sec != time2.sec)
		return(time1.sec > time2.sec);
	else if (time1.msec != time2.msec)
		return(time1.msec > time2.msec);
	else return false;
}
CLyrics类在使用时只需给其构造函数传递文件名作为参数,再调用DisposeLyric()函数,使用GetLyric函数可以返回指定时间的歌词文本。