0x00 脚下的路
不知道为啥要写这个小标题,可能是年轻的心想体验一下苍老的感觉,抑或是少年的一阵迷茫。混沌的四年,终究还是入了这一行。从初时的不知,到现在的刚开始,中间的间隔竟是四年之久,想起了陈奕迅的《十年》,但却不像医生所唱的十年那么有故事。或许这四年有这四年的价值,这四年也应有所积累。少年狂应少发,更不是什么老夫,当然也没有什么资格。之后的路应该在脚下,应是一步一步。比较喜欢的一句话,不去想目的地,才能轻松踏上旅途。少些心血来潮,多些精专。
聊以此记,舒心之惑,明己之志。
0x01 歌词解析
这是一个用c语言完成的小项目,大神们称之为玩具。不过对于对于知识的积累确有不小的作用。对于自己也是很好的训练,吃透每个小项目,吃透每一个细节,故总结之。
何为歌词解析?效果如何。刚开始必然是一片迷茫,可使用过各种播放器的我们应该对此熟悉。这是平日的歌曲播放器的部分截图:
管中窥豹,实现后的具体效果大致也应如此。当然,歌词解析吗?更应该了解歌词是什么样子的,有什么样子的格式(我们都明白,一切的神秘下面都隐藏着简单的道理)。这是某个词文件的部分截图:
如此,大致便可有些许想法了:将歌词文件读取出来,逐行显示在屏幕上,并随着歌曲播放时间逐行滚动。
0x02 流程分析
通过观察歌词,可以想到使用链表存储歌词信息比较方便,因为歌词要一行一行显示,并且事先并不了解每首歌的歌词有多少句。可以将歌词的每一句存入链表的一个节点。
整个流程大致可以分为如下几块:
读取歌词文件
按行切割歌词文件
逐行分析歌词头部歌曲信息部分
逐行分析歌词正文部分,并根据每行歌词前的时间将歌词插入链表
模拟时钟,根据时间显示歌词和播放歌曲
歌词滚屏,歌词反显(当前歌词突出显示)
0x03 读取歌词文件
文本文件的读取大致分如下步骤:
打开歌词文件
计算歌词文件的大小
分配内存空间
将歌词读入内存
关闭歌词文件
/*读取歌词*/
void read_lrc(char *p_lrc, char **lrc_data)
{
/*打开歌词文件*/
FILE *fp = fopen(p_lrc, "r");
if(fp == NULL)
{
perror("fopen");
return;
}
/*计算歌词文件的大小*/
fseek(fp, 0, 2);
long lrc_len = ftell(fp);
rewind(fp);
/*分配内存空间*/
*lrc_data = (char *)calloc(1, lrc_len);
if(*lrc_data == NULL)
{
perror("ralloc");
return;
}
/*将歌词读入内存*/
fread(*lrc_data, lrc_len, 1, fp);
/*关闭文件*/
fclose(fp);
return;
}
0x04 按行切割歌词文件
歌词切割应注意如下(百度百科):
/*将歌词逐行分割*/
void lrctok(char ** buf, char *lrc_data)
{
buf[0] = lrc_data;
int i = 0;
while((buf[i] = (char *)strtok(buf[i], "\r\n")))
i++;
return;
}
0x05 逐行分析歌词头部
歌词头部信息格式(百度百科):
由于头部信息比较简单,所以并不把头部信息存入链表,而是直接分析后显示:
/*分析歌词头部信息*/
int analysis_lrc_head(char **buf)
{
clear_screen();
cusor_moveto(0, 0);
set_fg_color(COLOR_GREEN);
/*逐行分析头部*/
int i = 0;
while(*(buf[i] + 1) > '0')/*解释歌词信息*/
{
/*读取头部信息内容*/
char lrc[128]="";
sscanf(buf[i],"%*[^:]:%[^]]", lrc);
/*读取头部信息标签*/
char tags[10] = "";
sscanf(buf[i], "[%[^:]", tags);
cusor_moveto(35, i+1);
if(strcmp(tags, "ti") == 0)
{
printf("歌名:");
}
else if(strcmp(tags, "ar") == 0)
{
printf("歌手:");
}
else if(strcmp(tags, "al") == 0)
{
printf("专辑:");
}
else if(strcmp(tags, "by") == 0)
{
printf("制作:");
}
else if(strcmp(tags, "offset") == 0)
{
printf("offset:");
}
printf("%s\n", lrc);
i++;
}
//printf("%d\n", i);
return i;
}
0x06 逐行分析歌词正文
对于歌词正文部分,要处理的有两个方面:一方面分析每句歌词前面的时间,另一方面,将根据前面的时间将歌词插入链表。
/*分析歌词正文部分*/
int analysis_lrc_body(char **buf,int star_num, List *list)
{
int i = star_num;/*从头部信息之后开始分析*/
while(buf[i])/*解析歌词正文*/
{
char *str_lrc = buf[i];
while(*str_lrc == '[')/*寻找歌词位置*/
str_lrc +=10;
//printf("%s\n", str_lrc);/*打印当前歌词测试*/
char *str_time = buf[i];/*解析每句歌词前的时间*/
while(*str_time == '[')
{
int m = 0,s = 0;
sscanf(str_time,"[%d:%d.%*d]", &m, &s);
int time = m*60+s;//以秒为单位
/*将时间和歌词 --对应 放入结构体*/
LRC *tmp = (LRC *)calloc(1, sizeof(LRC));
if(tmp == NULL)
{
perror("calloc");
return i;
}
tmp->time = time;
strcpy(tmp->lrc, str_lrc);
//调用链表的有序插入函数
list_ins_sort(list, tmp);
//分析下一个时间
str_time += 10;
}
i++;
}
return i;
}
0x07 模拟时钟
使用模拟时钟的目的是模拟实现歌曲播放与歌词同步,模拟时钟和歌曲播放器同时启动,然后根据模拟时钟的时间在歌词链表中查找。
int i = 0;/*模拟计时器*/
while(1)
{
/*根据时间i到链表中查找,并且打印歌词*/
time_delay(1);
i++;
}
0x08 歌词滚屏、歌词反选
/*模拟时钟*/
int i = 0;/*模拟计时器*/
char show[5][128] = {"","","","",""};
while(1)
{ </