一、项目介绍
攀登的过程也许漫长,但巅峰的风景是值得的!
C语言的一个小项目,在与虚拟机连接的Visual Studio Code中编写实现的,可以实现歌词的滚屏,进度条、以及时间显示等。
二、了解歌词
了解歌词,前四行都是歌词信息,需要单独处理;剩下的是歌词内容,可以综合处理。
歌词展示如下:
三、项目流程
1.读取歌词
1.1)打开歌词文件(fopen)
fopen:打开文件
1.2)计算歌词文件大小(fseek、ftell、rewind、calloc、fread、fclose)
fessk:将文件流指针指向末尾
ftell:获取文件流指针到文件首部的字节数
rewind:复位,将文件流指针指向开头
1.3)分配内存空间(calloc)
calloc:开辟空间
1.4)将歌词读入内存,并关闭文件
fread:将读取到的歌词,存入内存
fclose:关闭文件
//读取歌词
void read_lrc(char *p_lrc, char **lrc_data)
{
//打开歌词文件
FILE *fp = fopen(p_lrc, "r");
if(fp == NULL)
{
perror("fopen");
return;
}
//计算歌词文件的大小
fseek(fp, 0, SEEK_END);//fseek将文件流指针指向文件的末尾
long lrc_len = ftell(fp);//ftell获取文件流指针到文件首部的字节数
rewind(fp); //复位,将文件指针流指向开头
//根据文件大小,分配内存空间
*lrc_data = (char *)calloc(1, lrc_len);
if(*lrc_data == NULL)
{
perror("ralloc");
return;
}
//使用fread将读取到的歌词读入内存
fread(*lrc_data, lrc_len, 1, fp);
/*关闭文件*/
fclose(fp);
return;
}
2.按行切割歌词
将歌词逐行进行切割,每一行内容按照“\r\n”切割,将数据保存到数组buf中
//将歌词逐行分割
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;
}
3.逐行分析前四行歌词
将“ti”,“ar”,“al”,“by”转换为“歌名”,“歌手”,“专辑”,“制作”
//逐行分析头部
int i = 0;
while(*(buf[i] + 1) > '0')//解释歌词信息
{
//读取头部信息内容
char lrc[128]="";
sscanf(buf[i],"%*[^:]:%[^]]", lrc);
//读取头部信息标签“ti”,“ar”,“al”,“by”
char tags[10] = "";
sscanf(buf[i], "[%[^:]", tags);
cusor_moveto(35, i+1);
//将“ti”,“ar”,“al”,“by”转换为“歌名”,“歌手”,“专辑”,“制作”
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;
}
4.逐行分析歌词正文内容
主要分为时间和歌词两个部分,首先分析时间和歌词,然后根据时间将歌词插入链表
//分析歌词正文部分
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;
}
5.歌词滚屏
显示歌词设置为五行,调整歌词与歌曲同步偏差,根据时间显示歌词。
//模拟时钟
int i = 0;//模拟计时器
char show[5][128] = {
"","","","",""};
while(1)
{
//显示进度条
int base = 15;//进度条从第base列起始
set_fg_color(COLOR_RED);//颜色
cusor_moveto(base, line + 9);
printf("%02d:%02d|",i/60,i%60);//以mm:ss格式打印时间
fflush(stdout);
int endtime = ((LRC *)(list.tail->data))->time;
int length = (i*1.0)/endtime*45;
cusor_moveto(base + 6, line + 9);
int j = 0;
for(; j < length; j++)
{
printf(">");
}
cusor_moveto(base + 51, line + 9);
printf("|%02d:%02d",endtime/60, endtime%60);
//查找此时的歌词
int offset = 0;//调整歌词与歌曲同步偏差
LRC tmp;//按时间查找歌词
tmp.time = i + offset;//初始化查找时间
strcpy(tmp.lrc, "");
Node *ret = list_search(&list, &tmp);//返回查找歌词在链表中的位置
if(ret != NULL)
{
//滚动显示歌词
strcpy(show[0], show[1]);
strcpy(show[1], show[2]);
strcpy(show[2], show[3]);
strcpy(show[3], show[4]);
strcpy(show[4], ((LRC *)(ret->data))->lrc);
cusor_moveto(30, line+2);
set_fg_color(COLOR_WHITE);
printf("%s \n", show[0]);
fflush(stdout);
cusor_moveto(30, line+3);
printf("%s \n", show[1]);
fflush(stdout);
cusor_moveto(30, line+4);
printf("%s \n", show[2]);
fflush(stdout);
cusor_moveto(30, line+5)