C++读取歌词(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]胸の中だけで育ててた
- 读取歌词文件中和每一行,将每一行歌词(包括时间标签)存入一个string容器中。
- 依次处理刚刚得到的string容器中的每一个字符串。
- 查找字符串的最后一个右中括号“]”,将最后一个右中括号后面的字符作为歌词文本。
- 依次从第一个字符开始开始查找左中括号“[”,将右边两个字符作为分钟数,其右边第4个字符开始两个字符作为秒钟数,第7个字符开始为毫秒数。
- 将得到到时间标签和文件作为一句歌词存入容器。
- 继续查找左中括号“[”,如果找到了则重复4、5步骤,否则处理下一句歌词。
#pragma once
#include<string>
#include<vector>
#include<fstream>
#include<iostream>
#include<algorithm>
#include"Common.h"
using std::ifstream;
using std::string;
using std::wstring;
using std::vector;class CLyrics
{
private:struct Lyric{Time time;wstring text;bool operator<(const Lyric& lyric) const //重载小于号运算符,用于对歌词按时间标签排序{return lyric.time > time;}};wstring m_file; //歌词文件的文件名vector<Lyric> m_lyrics; //储存每一句歌词(包含时间标签和文本)vector<string> m_lyrics_str; //储存未拆分时间标签的每一句歌词CodeType m_code_type{ CodeType::ANSI }; //歌词文本的编码类型wstring m_ti; //歌词中的ti标签void DivideLyrics(); //将歌词文件拆分成若干句歌词,并保存在m_lyrics_str中void DisposeLyric(); //获得歌词中的时间标签和歌词文本,并将文本从string类型转换成wstring类型,保存在m_lyrics中void JudgeCode(); //判断歌词的编码格式public:CLyrics(wstring& file_name);CLyrics(){}bool IsEmpty() const; //判断是否有歌词wstring GetLyric(Time time, int offset) const; //根据时间返回一句歌词。第2个参数如果是0,则返回当前时间对应的歌词,如果是-1则返回当前时间的前一句歌词,1则返回后一句歌词,以此类推。int GetLyricProgress(Time time) const; //根据时间返回该时间所对应的歌词的进度(0~1000)(用于使歌词以卡拉OK样式显示)int GetLyricIndex(Time time) const; //根据时间返回该时间对应的歌词序号(用于判断歌词是否有变化)CodeType GetCodeType() const; //获得歌词文本的编码类型
};
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::CLyrics(wstring& file_name) : m_file{ file_name }
{DivideLyrics();JudgeCode();DisposeLyric();std::sort(m_lyrics.begin(), m_lyrics.end()); //将歌词按时间标签排序
}
void CLyrics::DivideLyrics()
{ifstream OpenFile{ m_file };string current_line;while (!OpenFile.eof()){std::getline(OpenFile, current_line); //从歌词文件中获取一行歌词m_lyrics_str.push_back(current_line);}
}
void CLyrics::JudgeCode()
{if (!m_lyrics_str.empty()) //确保歌词不为空{//有BOM的情况下,前面3个字节为0xef(-17), 0xbb(-69), 0xbf(-65)就是UTF8编码if (m_lyrics_str[0].size() >= 3 && (m_lyrics_str[0][0] == -17 && m_lyrics_str[0][1] == -69 && m_lyrics_str[0][2] == -65)) //确保m_lyrics_str[0]的长度大于或等于3,以防止索引越界{m_code_type = CodeType::UTF8;}else //无BOM的情况下{int i, j;bool break_flag{ false };for (i = 0; i < m_lyrics_str.size(); i++) //查找每一句歌词{if (m_lyrics_str[i].size() <= 16) continue; //忽略字符数为6以下的歌词(时间标签占10个字符),过短的字符串可能会导致将ANSI编成误判为UTF8for (j = 0; j < m_lyrics_str[i].size(); j++) //查找每一句歌词中的每一个字符{if (m_lyrics_str[i][j] < 0) //找到第1个非ASCII字符时跳出循环{break_flag = true;break;}}if (break_flag) break;}if (i<m_lyrics_str.size() && IsUTF8Bytes(m_lyrics_str[i].c_str())) //判断出现第1个非ASCII字符的那句歌词是不是UTF8编码,如果是歌词就是UTF8编码m_code_type = CodeType::UTF8_NO_BOM;}}
}
void CLyrics::DisposeLyric()
{int index;string temp;Lyric lyric;for (int i{ 0 }; i < m_lyrics_str.size(); i++){if (i==0){//查找ti:标签index = m_lyrics_str[i].find("ti:");int index2 = m_lyrics_str[i].find_first_of(']');if (index != string::npos) temp = m_lyrics_str[i].substr(index + 3, index2 - index - 3);m_ti = StrToUnicode(temp, m_code_type);}//获取歌词文本index = m_lyrics_str[i].find_last_of(']'); //查找最后一个']',后面的字符即为歌词文本if (index == string::npos) continue;temp = m_lyrics_str[i].substr(index + 1, m_lyrics_str[i].size() - index - 1);//将获取到的歌词文本转换成Unicodeif (temp.empty()) //如果时间标签后没有文本,显示为“……”lyric.text = L"……";elselyric.text = StrToUnicode(temp, m_code_type);//获取时间标签index = -1;while (true){index = m_lyrics_str[i].find_first_of('[', index + 1); //查找第1个左中括号if (index == string::npos) break; //没有找到左中括号,退出循环else if (index > m_lyrics_str[i].size() - 9) break; //找到了左中括号,但是左中括号在字符串的倒数第9个字符以后,也退出循环else if (m_lyrics_str[i][index + 1]>'9' || m_lyrics_str[i][index + 1] < '0') break; //找到了左中括号,但是左中括号后面不是数字,也退出循环temp = m_lyrics_str[i].substr(index + 1, 2); //获取时间标签的分钟数lyric.time.min = atoi(temp.c_str());temp = m_lyrics_str[i].substr(index + 4, 2); //获取时间标签的秒钟数lyric.time.sec = atoi(temp.c_str());if (m_lyrics_str[i][index + 8] == ']') //如果从左中括号往右数第8个字符就是右中括号了,说明这个时间标签的毫秒数只有1位{lyric.time.msec = m_lyrics_str[i][index + 7] - '0';lyric.time.msec *= 100;}else{temp = m_lyrics_str[i].substr(index + 7, 2); //获取时间标签的毫秒数(这里只取两位,乘以10后得到毫秒数)lyric.time.msec = atoi(temp.c_str()) * 10;}m_lyrics.push_back(lyric);}}
}
wstring CLyrics::GetLyric(Time time, int offset) const
{for (int i{ 0 }; i < m_lyrics.size(); i++){if (m_lyrics[i].time>time) //如果找到第一个时间标签比要显示的时间大,则该时间标签的前一句歌词即为当前歌词{if (i + offset - 1 < -1) return wstring{};else if (i + offset - 1 == -1) return m_ti; //时间在第一个时间标签前面,返回ti标签的值else if (i + offset - 1 < m_lyrics.size()) return m_lyrics[i + offset - 1].text;else return wstring{};}}if (m_lyrics.size() + offset - 1 < m_lyrics.size())return m_lyrics[m_lyrics.size() + offset - 1].text; //如果没有时间标签比要显示的时间大,当前歌词就是最后一句歌词elsereturn wstring{};
}
int CLyrics::GetLyricProgress(Time time) const
{int lyric_last_time{ 1 }; //time时间所在的歌词持续的时间int lyric_current_time{ 0 }; //当前歌词在time时间时已经持续的时间for (int i{ 0 }; i < m_lyrics.size(); i++){if (m_lyrics[i].time>time){if (i == 0){lyric_current_time = 0;lyric_last_time = 1;}else{lyric_last_time = m_lyrics[i].time - m_lyrics[i - 1].time;lyric_current_time = time - m_lyrics[i - 1].time;}if (lyric_last_time == 0) lyric_last_time = 1;return lyric_current_time * 1000 / lyric_last_time;}}//如果最后一句歌词之后已经没有时间标签,该句歌词默认显示20秒lyric_current_time = time - m_lyrics[m_lyrics.size() - 1].time;lyric_last_time = 20000;return lyric_current_time * 1000 / lyric_last_time;
}
int CLyrics::GetLyricIndex(Time time) const
{for (int i{ 0 }; i < m_lyrics.size(); i++){if (m_lyrics[i].time>time)return i - 1;}return m_lyrics.size() - 1;
}
inline CodeType CLyrics::GetCodeType() const
{return m_code_type;
}
inline bool CLyrics::IsEmpty() const
{return (m_lyrics.size() == 0);
}
Clyric类中使用到的全局函数及枚举类型的定义如下:
enum class CodeType
{ANSI,UTF8,UTF8_NO_BOM
};//将string类型的字符串转换成Unicode编码的wstring字符串
wstring StrToUnicode(const string& str, CodeType code_type)
{wchar_t str_unicode[256]{ 0 };int max{ 0 };if (code_type == CodeType::ANSI){max = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);if (max > 255) max = 255;MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, str_unicode, max);}else{max = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0);if (max > 255) max = 255;MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, str_unicode, max);}return wstring{ str_unicode };
}//判断一个字符串是否UTF8编码
bool IsUTF8Bytes(const char* data)
{int charByteCounter = 1; //计算当前正分析的字符应还有的字节数unsigned char curByte; //当前分析的字节.bool ascii = true;for (int i = 0; i < strlen(data); i++){curByte = static_cast<unsigned char>(data[i]);if (charByteCounter == 1){if (curByte >= 0x80){ascii = false;//判断当前while (((curByte <<= 1) & 0x80) != 0){charByteCounter++;}//标记位首位若为非0 则至少以2个1开始 如:110XXXXX...........1111110X if (charByteCounter == 1 || charByteCounter > 6){return false;}}}else{//若是UTF-8 此时第一位必须为1if ((curByte & 0xC0) != 0x80){return false;}charByteCounter--;}}if (ascii) return false; //如果全是ASCII字符,返回falseelse return true;
}
C++读取歌词(lrc)文件,分解歌词时间标签和歌词文本的方法相关推荐
- python两种方法读取、修改文件的创建时间、修改时间、访问时间
看到网上有人出于特种目前,需要修改文件的创建时间和修改时间(访问时间是只要在操作系统里打开文件,系统就会自动更改最后的访问时间,因此此时间无意义,于是在网上查阅结合自己的经验,归纳 一下可行方案,在 ...
- php中文歌词,详细介绍HTML5使用Audio标签实现歌词同步的效果
HTML5的最强大之处莫过于对媒体文件的处理,如利用一个简单的vedio标签就可以实现视频播放.类似地,在HTML5中也有对应的处理音频文件的标签,那就是audio标签.通过本文给大家介绍HTML5使 ...
- php更新时间就变成1970了,phpcms调用文章发布时间标签显示1970的解决方法
strtotime() 函数将任何英文文本的日期时间描述解析为 Unix 时间戳. 复制代码代码如下: {date('Y',strtotime($updatetime))} 大写Y显示 2013, 小 ...
- Android自定义View来实现解析lrc歌词同步滚动、上下拖动、缩放歌词等功能
http://blog.csdn.net/ouyang_peng/article/details/50813419 前言 一LRC歌词文件简介 1什么是LRC歌词文件 2LRC歌词文件的格式 LRC歌 ...
- LRC歌词解析,实现Linux设备播放音乐显示歌词 LRC解析
开始正文~~~ 1.关于LRC lrc是英文lyric(歌词)的缩写,被用做歌词文件的扩展名.以lrc为扩展名的歌词文件可以在各类数码播放器中同步显示.LRC 歌词是一种包含着"*:*&qu ...
- html怎么读取lrc文件,lrc文件怎么打开?lrc是什么文件?
lrc文件怎么打开?lrc是什么文件? lrc是歌词 文件的扩展名,一般用记事本打开. 关于lrc是什么文件?lrc是英文lyric(歌词)的缩写,被用做歌词文件的扩展名.以lrc为扩展名的歌词文件可 ...
- 用批处理整理百度MP3上歌曲排行榜MP3及LRC文件的批量下载链接地址(含图文教程)
http://bbs.wuyou.com/viewthread.php?tid=192322 本文结构如下: 一.缘起:问题的提出 二.试探:徒劳而返 三.峰回路转:芝麻!开门! 四.万事俱备:xml ...
- python歌词统计单词词频_Python爬虫网易云歌词及词频统计
采用词云对邓紫棋的热门前50歌曲进行可视化展示. 本次可视化步骤需要掌握的内容有:了解爬虫的原理 掌握xpath的用法 掌握词云工具wordcloud的使用 了解分词根据jieba的使用 首先,需要找 ...
- 小米手机安装https证书报错:无法安装该证书 因为无法读取该证书文件
Fiddler]手机安装https证书报错:无法安装该证书 因为无法读取该证书文件 之前在手机上使用 "ip:端口号" 的方法就能直接在手机上自动下载安装fiddler证书,但是现 ...
最新文章
- HDUOJ---2112HDU Today
- 怎么删除结构体数组中的一组数据_数据结构-栈
- 多线程处理海量数据的解决方案
- kafka偏移量保存到mysql里_Kafka 新版消费者 API(二):提交偏移量
- 关于云原生,这是最详细的技术知识
- css属性 content
- 人工智能切入垂直领域 风口已至?
- php dns失败,dns错误是什么意思
- leetcode python3 简单题225. Implement Stack using Queues
- 网络安全实验三 PGP 实现邮件加密和签名
- 人脸识别,结构光名词记录
- 亚马逊智能音箱无故发出笑声,多名用户被吓尿
- zookeeper入门篇
- vue3 + router-view + keepalive parentComponent.ctx.deactivate is not a function
- M480 EMAC驱动01-EMAC底层接口
- 新一代iPad Pro外形泄露:方形后摄瞩目
- Steamsets安装教程
- canvas - 基础知识 - 绘制剪纸图形
- mysql-8.0.16-winx64_mysql-8.0.16-winx64的最新安装教程
- 直流无刷电机霍尔传感器2种安装方式
热门文章
- 写在2021最后一天
- 孟岩:通证经济设计的七个原则,八个陷阱和十一个模板
- Android 刘海屏 适配
- APP移动应用测试策略与工具思维导图
- 专访中国信通院云大所栗蔚:ChatGPT的成功揭示了云计算作为数字世界“中枢神经”的价值
- [洛谷P3975][TJOI2015]弦论
- 神经网络架构搜索(NAS)综述
- 利用aether api实现从指定maven仓库下载jar包
- python爬取网站时,一键获取headers、url等信息(真的是让我爬取网站时,省了不少力气,作为小秘密分享给大家喽)
- 网络爬虫在业务中的应用