• 分享
  • lrc歌词命令行播放工具+wav播放(AI生成)

  • @ 2025-12-6 10:41:26

加上以下编译参数(devc++可能不行,编译器太老了,至少c++17) -lwinmm -std=c++17

lrc文件可以在笑匠音乐歌词库下载

wav可以下载bilibili视频,再转换为wav bilibili视频下载:bilibili视频下载器 mp4转wav:MP4转WAV

命令参数:music_player.exe test.wav test.lrc

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <regex>
#include <chrono>
#include <thread>

// Windows核心头文件(PlaySound专属)
#ifdef _WIN32
#include <windows.h>
#include <mmsystem.h>
// PlaySound需要链接winmm.lib,MinGW编译时仍需加-lwinmm
#else
#include <cstdlib>
#endif

// 设置控制台编码为UTF-8
void setConsoleUtf8() {
#ifdef _WIN32
    SetConsoleOutputCP(CP_UTF8);
    SetConsoleCP(CP_UTF8);
#endif
}

// 跨平台清屏
void clearScreen() {
#ifdef _WIN32
    system("cls");
#else
    system("clear");
#endif
}

// 播放WAV音频(改用PlaySound,100%兼容)
bool playAudio(const std::string& audioPath) {
    // 检查文件是否存在(先做前置校验)
    std::ifstream file(audioPath, std::ios::binary);
    if (!file.is_open()) {
        std::cerr << "错误:音频文件不存在 \"" << audioPath << "\"" << std::endl;
        return false;
    }
    file.close();

    // PlaySound参数说明:
    // SND_FILENAME:指定路径为文件名
    // SND_ASYNC:异步播放(不阻塞歌词显示)
    // SND_LOOP:循环播放(和之前的repeat效果一致)
    BOOL ret = PlaySoundA(audioPath.c_str(), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
    if (!ret) {
        std::cerr << "错误:无法播放WAV音频文件 \"" << audioPath << "\"" << std::endl;
        std::cerr << "原因:WAV文件损坏/不是标准PCM格式WAV" << std::endl;
        return false;
    }

    std::cout << "音频播放已启动:" << audioPath << std::endl;
    return true;
}

// 停止音频播放
void stopAudio() {
    PlaySoundA(NULL, NULL, 0); // 传入NULL停止所有播放
    std::cout << "\n音频已停止播放" << std::endl;
}

// 时间字符串转毫秒
long long timeStrToMs(const std::string& timeStr) {
    std::regex pattern(R"((\d+):(\d+)\.(\d+))");
    std::smatch matches;
    
    if (std::regex_match(timeStr, matches, pattern)) {
        int minutes = std::stoi(matches[1]);
        int seconds = std::stoi(matches[2]);
        int milliseconds = std::stoi(matches[3]);
        if (matches[3].length() == 1) milliseconds *= 100;
        else if (matches[3].length() == 2) milliseconds *= 10;
        return minutes * 60 * 1000 + seconds * 1000 + milliseconds;
    }
    return -1;
}

// 解析LRC文件
std::map<long long, std::string> parseLRC(const std::string& filename) {
    std::map<long long, std::string> lrcMap;
    std::ifstream file(filename, std::ios::binary);
    
    if (!file.is_open()) {
        std::cerr << "错误:无法打开歌词文件 \"" << filename << "\"" << std::endl;
        std::cerr << "当前工作目录:";
#ifdef _WIN32
        char buffer[MAX_PATH];
        GetCurrentDirectoryA(MAX_PATH, buffer);
        std::cerr << buffer << std::endl;
#endif
        return lrcMap;
    }

    std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
    file.close();

    // 移除UTF-8 BOM
    if (content.size() >= 3 && (unsigned char)content[0] == 0xEF && 
        (unsigned char)content[1] == 0xBB && (unsigned char)content[2] == 0xBF) {
        content = content.substr(3);
    }

    std::regex lineRegex(R"(\r?\n)");
    std::sregex_token_iterator it(content.begin(), content.end(), lineRegex, -1);
    std::sregex_token_iterator end;

    std::regex linePattern(R"(\[(\d+:\d+\.\d+)\](.*))");
    std::smatch matches;

    for (; it != end; ++it) {
        std::string line = *it;
        if (line.empty()) continue;
        
        if (std::regex_match(line, matches, linePattern)) {
            std::string timeStr = matches[1];
            std::string lyric = matches[2];
            lyric = std::regex_replace(lyric, std::regex("^\\s+|\\s+$"), "");
            
            long long ms = timeStrToMs(timeStr);
            if (ms != -1 && !lyric.empty()) {
                lrcMap[ms] = lyric;
            }
        }
    }

    std::cout << "成功解析到 " << lrcMap.size() << " 句歌词" << std::endl;
    return lrcMap;
}

// 同步显示歌词
void playLyricsSync(const std::map<long long, std::string>& lrcMap) {
    if (lrcMap.empty()) {
        std::cout << "没有可显示的歌词" << std::endl;
        return;
    }

    clearScreen();
    long long currentTime = 0;
    auto it = lrcMap.begin();
    std::string lastLyric = "";

    std::cout << "开始同步显示歌词(按Ctrl+C停止):\n" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 等音频启动

    while (currentTime <= 5 * 60 * 1000 && it != lrcMap.end()) { // 最多5分钟
        if (currentTime >= it->first) {
            if (it->second != lastLyric) {
                clearScreen();
                std::cout << "==============================" << std::endl;
                std::cout << "\t\t" << it->second << std::endl;
                std::cout << "==============================" << std::endl;
                std::cout << "\n播放进度:" << currentTime / 1000 << "秒" << std::endl;
                lastLyric = it->second;
            }
            ++it;
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        currentTime += 100;
    }
}

int main(int argc, char* argv[]) {
    setConsoleUtf8();
    
    if(argc != 3) {
        std::cout << "使用方法:music_player.exe 音频文件路径 歌词文件路径" << std::endl;
        std::cout << "示例:music_player.exe test.wav test.lrc" << std::endl;
        std::cout << "仅支持WAV格式(必须是标准PCM编码)" << std::endl;
        return 1;
    }

    std::string audioFile = argv[1];
    std::string lrcFile = argv[2];
    
    std::cout << "当前程序运行目录:";
#ifdef _WIN32
    char buffer[MAX_PATH];
    GetCurrentDirectoryA(MAX_PATH, buffer);
    std::cout << buffer << std::endl;
#endif
    std::cout << "正在尝试打开音频:" << audioFile << std::endl;
    std::cout << "正在尝试打开歌词:" << lrcFile << std::endl;

    if (!playAudio(audioFile)) return 1;
    auto lrcMap = parseLRC(lrcFile);
    playLyricsSync(lrcMap);
    stopAudio();

    return 0;
}

1 comments

  • @ 2025-12-6 10:47:58

    这是一个lrc的示例:

    [00:03.952]ねえあたし知ってるよ きみがひとり“XX”してるの知ってるよ
    [00:08.408]ビクンビクン震えてさ 声もダダ漏れなんだわ
    [00:11.205]正直に言っちゃえよ バレてるんだし言っちゃえよ 効いてんの?
    [00:15.782]普通普通 恥ずかしい?みんな隠しているだけ
    [00:18.522]
    [00:25.828]ねえあたし知ってるよ きみがひとり“涙”してるの知ってるよ
    [00:30.294]グスングスン凹んでさ 弱音ヒトカラ in the night
    [00:33.063]朝が来るまで一緒コース もっと泣いたって
    [00:35.507]何度だって受け止めてあげる
    [00:37.778]もう我慢しないでいっぱい出してね
    [00:40.255]
    [00:40.311]MWAH!
    [00:41.395]お願い きみが欲しいの
    [00:43.497]慰めさせてシェイクシェイク 愛の才能で
    [00:47.131]泣いてくれなきゃ 涸れてしまう 濡れていたい
    [00:50.459]ねえいいでしょう? 舐め取って 飲み干したいんだってば
    [00:54.837]
    [00:54.838]MWAH!
    [00:55.898]お願い きみが欲しいの
    [00:58.087]頼り散らしてシックラブ なんて最高ね
    [01:01.666]分けてくれなきゃ 君の“痛い”感じていたい
    [01:05.049]ねえいいでしょう? 吸い取って 救いたいんだってば
    [01:09.708]
    [01:10.420]見たいの きみの中
    [01:17.449]
    [01:23.925]ねえあたし知ってるよ きみがひとり悔しがってんの知ってるよ
    [01:28.437]ズキュンズキュン高まるじゃん きみを推すことをやめない
    [01:31.290]ねえあたし知ってるよ きみはできる子 知ってるよ
    [01:34.879]つらい時は弱いくらいで丁度いい あたしそれでも好きだよ
    [01:38.469]
    [01:38.470]MWAH!
    [01:39.521]お願い きみが欲しいの
    [01:41.772]名前を呼んでよ いつだって会いに参上
    [01:45.391]きみはひとりだ だから歌う「ひとりじゃない」
    [01:48.623]もういいでしょう
    [01:49.693]ソロプレイはお仕舞いなんだってば
    [01:53.545]
    [01:53.982]きみが病めるときも あたし側にいるわ
    [02:00.857]いつも見守っているわ そうよ 怖くないのよ
    [02:09.991]
    [02:11.295]MWAH!
    [02:12.214]お願い きみが欲しいの
    [02:14.465]慰めさせてシェイクシェイク 愛の才能で
    [02:18.091]泣いてくれなきゃ 涸れてしまう 濡れていたい
    [02:21.266]ねえいいでしょう? 舐め取って 飲み干したいんだってば
    [02:25.836]
    [02:25.837]MWAH!
    [02:26.815]お願い きみが欲しいの
    [02:29.007]頼り散らしてシックラブ なんて最高ね
    [02:32.659]分けてくれなきゃ 君の“痛い”感じていたい 覗いていたい
    [02:36.879]吸い取って 救いたいんだってば
    [02:40.328]
    [02:40.329]ねえあたし知ってるよ きみがひとり“涙”してるの知ってるよ
    [02:44.783]グスングスン凹んでさ 弱音ヒトカラ in the night
    [02:47.626]朝が来るまで一緒コース もっと泣いたって
    [02:50.136]何度だって受け止めてあげる
    [02:52.348]もう我慢しないで出してってば
    [02:54.768]
    [02:54.769]さあ
    [02:55.955]
    

    曲名:モニタリング lrc歌词来源

  • 1