Skip to content

歌词源及解析器脚本

ESLyric edited this page Sep 7, 2024 · 15 revisions

前言

ESLyric 使用了QuickJS引擎的移植版本 提供自定义外部歌词源等功能,引擎中std及os模块未导出。

现提供的脚本可至仓库scripts查看。

歌词源接口

歌词源脚本位置:fb2k配置目录\eslyric-data\scripts\searcher

0. 歌词源配置信息

interface IESLyricSearchServiceConfig
{
properties:
    [r,w] string name; // 歌词源名称
    [r,w] string author; // 歌词源作者
    [r,w] string version; // 歌词源版本
}

1. 音轨描述信息

interface IESLyricTrackMetadb
{
properties:
    [r] string title; // 音轨标题,若启用了搜索预处理,则该字段为预处理后的关键词,否则与rawTitle相同
    [r] string artist; // 音轨艺术家,若启用了搜索预处理,则该字段为预处理后的关键词,否则与rawArtist相同
    [r] string album; // 音轨专辑,若启用了搜索预处理,则该字段为预处理后的关键词,否则与rawAlbum相同
    [r] string rawTitle; // 未经过预处理的音轨标题
    [r] string rawArtist; // 未经过预处理的音轨艺术家
    [r] string rawAlbum; // 未经过预处理的音轨专辑
    [r] string rawPath; // 完整路径,如file://c:\path\to\track.mp3
    [r] string path; // 显示路径,如c:\path\to\track.mp3
    [r] int subSong; // 子音轨索引
    [r] double duration; // 音轨长度
};

2. 歌词描述信息

interface IESLyricMetaInfo
{
properties:
    [r,w] string title; // 歌词所属标题,必填字段
    [r,w] string artist; // 歌词所属艺术家,推荐填写,显示结果更准确
    [r,w] string album; // 歌词所属专辑,在搜索界面显示
    [r,w] string lyricText; // 歌词文本, 与lyricData字段二者必填一项
    [r,w] ArrayBuffer lyricData; // 歌词数据, 如为其他需要外部解析的歌词写入到该字段,与lyricText字段二者必填一项
    [r,w] string source; // 歌词来源,可选,未填写时自动填充对应歌词源名
    [r,w] string fileType; // 歌词文件类型,默认为lrc,为其他格式时需填写且需存在对应格式的解析器才能正常显示
    [r,w] string location; // 歌词位置,可选
    [r,w] bool isLocal; // 是否为本地歌词, 可选,默认为false
    [r,w] int confidence; // 歌词匹配度, 可选,未填写时使用内部判断值
};

3. 歌词管理接口

interface IESLyricMetaInfoManager
{
Methods:
   IESLyricMetaInfo createLyric(); // 创建一个新的歌词描述信息(多个外部歌词可共用同个描述信息接口进行设置)
   void addLyric(IESLyricMetaInfo newLyric); // 添加歌词至ESLyric
   bool checkAbort(); // 用户是否取消搜索,是则返回true,否则返回false
   string getSvcData(string key); // 获取歌词源相关配置
   void setSvcData(string key); // 设置歌词源相关配置,key在同一歌词源内唯一即可
}

4. HttpClient相关接口

interface IHttpResponse
{
properties:
   [r] int statusCode; // http状态值
   [r] string statusMessage; // 状态信息
   [r] object headers; // 响应头对象, 如 var encoding = headers['content-encoding'];
}

// 全局函数

// 发起http请求
// 参数options为以下两类值:
// 1. 字符串URL 如 request("https://www.example.com", ...);
// 2. 请求的配置对象,该对象支持四个字段,如reqeust({url: "https://www.example.com", method: "get", body: "post data", { Referer: "www.example.com" }}, ...);
// object HttpRequestSettings
//{
//   string url; // 请求的url
//   string method; // 请求的方式, "get"或"post"
//   string body; // post data
//   object headers; // 请求头部
//   bool raw; // 返回原始数据, body为js array类型
//}
// 参数callback为请求结束后的回调 void callback(int err, IHttpResponse rsp, string body)
void request(object options, func callback);

5. 歌词源导出回调

// 获取歌词源信息时调用
export function getConfig(IESLyricSearchServiceConfig cfg)
{
}

// 获取歌词时调用
export function getLyrics(IESLyricTrackMetadb meta, IESLyricMetaInfoManager man)
{
}

上述两个回调函数每个歌词源都必须提供。

6. minixml模块

ESLyric中使用minixml解析xml文件,也提供了相关脚本接口。

// 全局对象mxml
interface IMiniXml
{
constants:
    MXML_DESCEND;
    MXML_NO_DESCEND;
    MXML_DESCEND_FIRST;
    MXML_IGNORE;
    MXML_ELEMENT;
    MXML_INTEGER;
    MXML_OPAQUE;
    MXML_REAL;
    MXML_TEXT;
    MXML_CUSTOM;
    
    // callback types, loadString中使用
    MXML_NO_CALLBACK;
    MXML_TEXT_CALLBACK;
    MXML_INTEGER_CALLBACK;
    MXML_OPAQUE_CALLBACK;
    MXML_REAL_CALLBACK;
    MXML_IGNORE_CALLBACK;


Methods:
    IMiniXmlNode loadString(string xmlText, int callbackType = MXML_NO_CALLBACK);// 加载xml文本
}

interface IMiniXmlNode
{
Methods:
    // https://www.msweet.org/mxml/mxml.html#mxmlFindElement
    IMiniXmlNode findElement(string element, string attr, string val, int decend);
    // https://www.msweet.org/mxml/mxml.html#mxmlFindPath
    IMiniXmlNode findPath(string path);
    // https://www.msweet.org/mxml/mxml.html#mxmlElementGetAttr
    string getAttr(string attrName);
    // https://www.msweet.org/mxml/mxml.html#mxmlElementGetAttrCount
    int getAttrCount();
    // https://www.msweet.org/mxml/mxml.html#mxmlElementGetAttrByIndex
    object getAttrByIndex(int idx)
    // https://www.msweet.org/mxml/mxml.html#mxmlGetElement
    string getElement();
    // https://www.msweet.org/mxml/mxml.html#mxmlGetCDATA
    string getCDATA();
    // https://www.msweet.org/mxml/mxml.html#mxmlGetFirstChild
    IMiniXmlNode getFirstChild();
    // https://www.msweet.org/mxml/mxml.html#mxmlGetLastChild
    IMiniXmlNode getLastChild();
    // https://www.msweet.org/mxml/mxml.html#mxmlGetPrevSibling
    IMiniXmlNode getPrevSibling();
    // https://www.msweet.org/mxml/mxml.html#mxmlGetNextSibling
    IMiniXmlNode getNextSibling();
    // https://www.msweet.org/mxml/mxml.html#mxmlGetParent
    IMiniXmlNode getParent();
    // https://www.msweet.org/mxml/mxml.html#mxmlGetInteger
    int getInteger();
    // https://www.msweet.org/mxml/mxml.html#mxmlGetReal
    double getReal();
    // https://www.msweet.org/mxml/mxml.html#mxmlGetText
    string getText();
    // https://www.msweet.org/mxml/mxml.html#mxmlGetOpaque
    string getOpaque();
    // https://www.msweet.org/mxml/mxml.html#mxmlGetType
    int getType();
}

7. zlib模块

zlib模块依赖foobar2000安装目录下的zlib1.dll文件。

// 全局对象zlib
interface IZlib
{
Methods:
    arraybuffer compress(arraybuffer ab);
    arraybuffer uncompress(arraybuffer ab);
}

8. 其他

// 全局对象console
interface IConsole
{
Methods:
    void log(string msg); // 输出日志至foobar2000控制台,如console.log("hello!");
}

interface IFbProfiler
{
Methods:
    void reset();
    string queryString();
    double time();
}

// 全局对象utils
interface IFbUtils
{
Methods:
    IFbProfiler createProfiler(); // 创建一个profiler,如var profiler = utils.createProfiler();
}

// 全局函数

// 载入外部库,路径是相对于lib路径的相对路径,如evalLib("querystring/querystring.min.js");载入scripts\lib\querystring目录下的querystring.min.js库
void evalLib(string libRelativeLibPath); 
// base64解码
string atob(string b64text)
// base64编码
string btoa(string text);
// 将arraybuffer中数据直接转换为string
string arrayBufferToString(arraybuffer ab);
// 将string转换为arraybuffer(utf8)
arraybuffer stringToArrayBuffer(string s);

歌词源示例

示例歌词源脚本:

export function getConfig(cfg)
{
    cfg.name = "My custom source";
    cfg.version = "0.1";
    cfg.author = "My name";
    cfg.useRawMeta = false;
}

export function getLyrics(meta, man)
{
    let lyricMeta = man.createLyric();
    // 从网络获取歌词
    request("https://example.com/", (err, res, body) => {
        if (!err && res.statusCode == 200) {
            lyricMeta.title = "title";
            lyricMeta.artist = "artist";
            lyricMeta.album = "album"; // 可选
            lyricMeta.lyricText = body; // 设置歌词文本
            man.addLyric(lyricMeta); // 添加至ESLyric
        }
    });
}

歌词解析器接口

解析器脚本位置:eslyric安装文件目录\scripts\parser

实现解析器可以使ESLyric支持更多格式的歌词。解析器需要将其他格式歌词转换为ESLyric可识别的歌词。
当前ESLyric支持的歌词格式:

  1. 标准LRC
[00:00.00]line 1
[00:01.00]line 2
  1. 多标签增强LRC (见wiki Enhanced format部分说明)
[mm:ss.xx] <mm:ss.xx> line 1 word 1 <mm:ss.xx> line 1 word 2 <mm:ss.xx> ... line 1 last word <mm:ss.xx>
[mm:ss.xx] <mm:ss.xx> line 2 word 1 <mm:ss.xx> line 2 word 2 <mm:ss.xx> ... line 2 last word <mm:ss.xx>

因此解析器需要将其他格式的格式通过脚本转换为上述任一种形式。

解析器配置描述:

interface IParserConfig
{
properties:
    string name; // 解析器名称
    string author; // 解析器脚本作者
    string version; // 解析器版本
    string fileType; // 文件后缀,如"srt"
    bool parsePlainText; // 是否纯文本,影响parseLyric回调中传入的歌词数据,默认为true
}

解析器解析上下文描述:

interface IParserContext
{
Properties:
    [r,w] string lyricText; // 当IParserConfig中设置parsePlainText为true时有效,同时作为解析后的结果返回字段
    [r] arraybuffer lyricData; // 当IParserConfig中设置parsePlainText为false时有效
    [r] double duration; // 音轨长度(参考值)
}

解析器导出回调接口:

// 获取歌词源信息时调用
export function getConfig(IParserConfig cfg)
{
}

// 获取歌词时调用
export function parseLyric(IParserContext context)
{
}

歌词解析器示例

export function getConfig(IParserConfig cfg)
{
    cfg.name = "my custom parser";
    cfg.fileType = "srt";
}

export function parseLyric(IParserContext context)
{
    // 转换为ESLyric支持的标准或增强型lrc
    context.lyricText = myCustomConv(context.lyricText);
    // or context.lyricText = myCustomConv(context.lyricData);
}