浏览量
迁移自 CSDN 博客,大一的时候写的,稍作改动
LRC文件如上图所示。
格式为 [mm:ss.ms]歌词
但是也有单行多个时间轴的情况,即 [mm:ss.ms][mm:ss.ms]歌词
对于解析来说,比较困难的正是单行多个时间轴的情况。我的解决方法是:
(1) 对单独一行检索]
后没有[
的位置,即该行最后一个[]
框,将指针指向下一元素,就是这行歌词的第一个字。
(2) 编写函数将最后一个[]
里面mm:ss.ms
格式的时间轴化为 long 的 ms 。
(3) 删除最后一个[]
及里面的内容,将后面的歌词并上来重新构成该行(整个算法的核心就在这里)
(4) 重新检索该行最右边的[]
,直到没有[]
为止
(5) 开始检索下一行,重复(1)的操作,直到下一行也没有[]
为止。
(6) 整个歌词用链表储存,获取所有时间轴和歌词之后,按时间轴对链表进行排序。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib, "WINMM.LIB")
#define LEN sizeof(lyric)
typedef struct _lyric
{
long timeLine;
char verse[256];
struct _lyric* next;
} lyric;
lyric* Head = NULL;
long ms(char origin[9]) //将mm:ss.ms化为毫秒
{
long result = 0;
result = atoi(origin) * 60 * 1000 + atoi(origin + 3) * 1000 + atoi(origin + 6) * 10;
return result;
}
// 输出模块
void OutputLyrics()
{
lyric *p;
for (p = Head; p != NULL; p = p->next)
printf("%ld >> %s\n", p->timeLine, p->verse);
}
// 播放模块
void Play()
{
clock_t e, start;
lyric *p;
system("mode con cols=150 lines=3");
mciSendString("play D:\\C\\Jukebox\\Release\\audio\\ebbandflow.mp3", NULL, 0, 0);
mciSendString("setaudio D:\\C\\Jukebox\\Release\\audio\\ebbandflow.mp3 volume to 150", NULL, 0, 0);
start = clock();
while (1)
{
Sleep(200);
e = clock() - start;
printf("%ld\t", e);
p = Head;
while (!(p->next == NULL || (p->next)->timeLine >= e)) //检索当前应显示的歌词,要么是最后一句,要么下一句的时间轴在当前时间之后
p = p->next;
printf("%ld >> %-130.130s\r", p->timeLine, p->verse);
}
}
int main()
{
FILE *lrc = fopen("D:\\C\\Jukebox\\Release\\audio\\ebbandflow.lrc", "r");
char linePointer[256];
int i;
lyric *p, *q, temp, *tempMin;
// 让linePointer指向第一个歌词串
do
{
fgets(linePointer, 256, lrc);
}
while (!(linePointer[2] >= '0' && linePointer[2] <= '9'));
// 对每一行歌词进行操作
do
{
if (linePointer[strlen(linePointer) - 1] == '\n')
linePointer[strlen(linePointer) - 1] = '\0'; //删除末尾的回车
do
{
i = 0;
do
{
i++;
} while (!(linePointer[i - 1] == ']' && linePointer[i] != '[')); //让i指向最右边的[]右侧]的右边,即指向该行歌词的第一个字
p = (lyric*)malloc(LEN);
p->timeLine = ms(linePointer + i - 9);
strcpy(p->verse, linePointer + i);
if (Head == NULL)
Head = p;
else
q->next = p;
q = p;
linePointer[i - 10] = '\0'; //删除最后的括号
strcat(linePointer, q->verse); //巧妙地接回去便于继续解析
} while (linePointer[0] == '['); //解析到没有括号为止
} while (!(fgets(linePointer, 256, lrc) == NULL || linePointer[0] != '[')); //到最后一行
q->next = NULL;
fclose(lrc);
// 按时间轴排序
for (p = Head; p != NULL; p = p->next) //比较笨拙的选择排序,按时间轴从小到大
{
tempMin = p;
for (q = p->next; q != NULL; q = q->next)
if (tempMin->timeLine > q->timeLine) //找最小的
tempMin = q; //寻找比最小还要小的,用临时指针标记
if (tempMin != p) //如果确实找到了更小的
{
temp = *tempMin;
*tempMin = *p;
*p = temp;
temp.next = tempMin->next;
tempMin->next = p->next;
p->next = temp.next;
}
}
// 播放测试或打印到屏幕
Play();
// OutputLyrics();
return 0;
}
在我这边的环境下目前仅支持 ANSI 编码的 lrc 文件(Unicode 和 UTF-8 会变乱码)
同步播放的效果
按条导出的效果
本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。
本文链接:https://ekyu.moe/article/analyze-lrc-file-using-c/