diff --git a/build-config/build-before-pack.js b/build-config/build-before-pack.js index 87f1cc61..c4ea828e 100644 --- a/build-config/build-before-pack.js +++ b/build-config/build-before-pack.js @@ -3,26 +3,54 @@ const fsPromises = require('fs').promises const path = require('path') const { Arch } = require('electron-builder') -const fileNameMap = { +const better_sqlite3_fileNameMap = { [Arch.arm64]: 'arm64.glibc27', [Arch.armv7l]: 'armv7l', } +const qrc_decode_fileNameMap = { + win32: { + [Arch.x64]: 'windows.x64', + [Arch.ia32]: 'windows.x86', + [Arch.arm64]: 'windows.arm64', + }, + linux: { + [Arch.x64]: 'linux.x64', + [Arch.arm64]: 'linux.arm64', + [Arch.armv7l]: 'linux.armv7l', + }, + darwin: { + [Arch.x64]: 'mac.x86', + [Arch.arm64]: 'mac.arm64', + }, +} + const replaceSqliteLib = async(arch) => { // console.log(await fs.readdir(path.join(context.appOutDir, './resources/'))) // if (context.electronPlatformName != 'linux' || context.arch != Arch.arm64) return // https://github.com/lyswhut/lx-music-desktop/issues/1102 // https://github.com/lyswhut/lx-music-desktop/issues/1161 console.log('replace sqlite lib...') - const filePath = path.join(__dirname, `./lib/better_sqlite3.linux.${fileNameMap[arch]}.node`) + const filePath = path.join(__dirname, `./lib/better_sqlite3.linux.${better_sqlite3_fileNameMap[arch]}.node`) const targetPath = path.join(__dirname, '../node_modules/better-sqlite3/build/Release/better_sqlite3.node') await fsPromises.unlink(targetPath).catch(_ => _) await fsPromises.copyFile(filePath, targetPath) } +const replaceQrcDecodeLib = async(platform, arch) => { + console.log('replace qrc_decode lib...', platform, qrc_decode_fileNameMap[platform][arch]) + const filePath = path.join(__dirname, `./lib/qrc_decode.${qrc_decode_fileNameMap[platform][arch]}.node`) + const targetPath = path.join(__dirname, '../build/Release/qrc_decode.node') + const targetDir = path.dirname(targetPath) + if (fs.existsSync(targetDir)) await fsPromises.unlink(targetPath).catch(_ => _) + else await fsPromises.mkdir(targetDir, { recursive: true }) + await fsPromises.copyFile(filePath, targetPath) +} + module.exports = async(context) => { const { electronPlatformName, arch } = context + await replaceQrcDecodeLib(electronPlatformName, arch) if (electronPlatformName !== 'linux') return const bindingFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp') const bindingBakFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp.bak') diff --git a/build-config/lib/qrc_decode.linux.arm64.node b/build-config/lib/qrc_decode.linux.arm64.node new file mode 100644 index 00000000..28877a61 Binary files /dev/null and b/build-config/lib/qrc_decode.linux.arm64.node differ diff --git a/build-config/lib/qrc_decode.linux.armv7l.node b/build-config/lib/qrc_decode.linux.armv7l.node new file mode 100644 index 00000000..d09c5b57 Binary files /dev/null and b/build-config/lib/qrc_decode.linux.armv7l.node differ diff --git a/build-config/lib/qrc_decode.linux.x64.node b/build-config/lib/qrc_decode.linux.x64.node new file mode 100644 index 00000000..0b78f339 Binary files /dev/null and b/build-config/lib/qrc_decode.linux.x64.node differ diff --git a/build-config/lib/qrc_decode.mac.arm64.node b/build-config/lib/qrc_decode.mac.arm64.node new file mode 100644 index 00000000..42016e26 Binary files /dev/null and b/build-config/lib/qrc_decode.mac.arm64.node differ diff --git a/build-config/lib/qrc_decode.mac.x86.node b/build-config/lib/qrc_decode.mac.x86.node new file mode 100644 index 00000000..59da8333 Binary files /dev/null and b/build-config/lib/qrc_decode.mac.x86.node differ diff --git a/build-config/lib/qrc_decode.windows.arm64.node b/build-config/lib/qrc_decode.windows.arm64.node new file mode 100644 index 00000000..78fd18ca Binary files /dev/null and b/build-config/lib/qrc_decode.windows.arm64.node differ diff --git a/build-config/lib/qrc_decode.windows.x64.node b/build-config/lib/qrc_decode.windows.x64.node new file mode 100644 index 00000000..2b95161f Binary files /dev/null and b/build-config/lib/qrc_decode.windows.x64.node differ diff --git a/build-config/lib/qrc_decode.windows.x86.node b/build-config/lib/qrc_decode.windows.x86.node new file mode 100644 index 00000000..43df614c Binary files /dev/null and b/build-config/lib/qrc_decode.windows.x86.node differ diff --git a/build-config/main/webpack.config.base.js b/build-config/main/webpack.config.base.js index c62760d8..d0191e39 100644 --- a/build-config/main/webpack.config.base.js +++ b/build-config/main/webpack.config.base.js @@ -1,6 +1,8 @@ const path = require('path') const ESLintPlugin = require('eslint-webpack-plugin') +const isDev = process.env.NODE_ENV === 'development' + module.exports = { target: 'electron-main', output: { @@ -10,12 +12,13 @@ module.exports = { }, path: path.join(__dirname, '../../dist'), }, - externals: [ - 'font-list', - 'better-sqlite3', - 'bufferutil', - 'utf-8-validate', - ], + externals: { + 'font-list': 'font-list', + 'better-sqlite3': 'better-sqlite3', + bufferutil: 'bufferutil', + 'utf-8-validate': 'utf-8-validate', + 'qrc_decode.node': isDev ? path.join(__dirname, '../../build/Release/qrc_decode.node') : path.join('../build/Release/qrc_decode.node'), + }, resolve: { alias: { '@main': path.join(__dirname, '../../src/main'), diff --git a/package-lock.json b/package-lock.json index ecad9e9c..37d9a4c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "lx-music-desktop", - "version": "2.1.0-beta.4", + "version": "2.1.0-beta.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "lx-music-desktop", - "version": "2.1.0-beta.4", + "version": "2.1.0-beta.6", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index b08606fd..db578ff1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lx-music-desktop", - "version": "2.1.0-beta.4", + "version": "2.1.0-beta.6", "description": "一个免费的音乐查找助手", "main": "./dist/main.js", "productName": "lx-music-desktop", @@ -100,6 +100,7 @@ "node_modules/node-gyp-build", "node_modules/bufferutil", "node_modules/utf-8-validate", + "build/Release/qrc_decode.node", "dist/**/*" ], "asar": { diff --git a/publish/changeLog.md b/publish/changeLog.md index 4e8cc6b5..553ef572 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -3,7 +3,7 @@ - 新增桌面歌词设置字体加粗设置,可以到设置-桌面歌词设置-加粗字体修改 - 新增是否自动下载更新设置,默认开启,可以去设置-软件更新更改 - 新增当前版本更新日志显示弹窗(建议大家阅读更新日志以了解当前版本的变化),在更新版本后将自动弹出 -- 添加wy源逐字歌词的支持 +- 添加wy、tx源逐字歌词的支持 ### 优化 diff --git a/src/common/ipcNames.ts b/src/common/ipcNames.ts index 84c7b2ab..cb653317 100644 --- a/src/common/ipcNames.ts +++ b/src/common/ipcNames.ts @@ -73,6 +73,7 @@ const modules = { // lang_s2t: 'lang_s2t', handle_kw_decode_lyric: 'handle_kw_decode_lyric', + handle_tx_decode_lyric: 'handle_tx_decode_lyric', get_lyric_info: 'get_lyric_info', set_lyric_info: 'set_lyric_info', set_config: 'set_config', diff --git a/src/main/modules/winMain/rendererEvent/index.ts b/src/main/modules/winMain/rendererEvent/index.ts index d52915ba..1f3700d8 100644 --- a/src/main/modules/winMain/rendererEvent/index.ts +++ b/src/main/modules/winMain/rendererEvent/index.ts @@ -3,6 +3,7 @@ import { registerRendererEvents as list } from '@main/modules/commonRenderers/li import app, { sendConfigChange } from './app' import hotKey from './hotKey' import kw_decodeLyric from './kw_decodeLyric' +import tx_decodeLyric from './tx_decodeLyric' import userApi from './userApi' import sync from './sync' import data from './data' @@ -26,6 +27,7 @@ export default () => { app() hotKey() kw_decodeLyric() + tx_decodeLyric() userApi() sync() data() diff --git a/src/main/modules/winMain/rendererEvent/tx_decodeLyric.ts b/src/main/modules/winMain/rendererEvent/tx_decodeLyric.ts new file mode 100644 index 00000000..2ca5a750 --- /dev/null +++ b/src/main/modules/winMain/rendererEvent/tx_decodeLyric.ts @@ -0,0 +1,46 @@ +import { inflate } from 'zlib' +// import path from 'path' +import { mainHandle } from '@common/mainIpc' +import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames' + +// eslint-disable-next-line @typescript-eslint/dot-notation, @typescript-eslint/quotes +// const require = module[`require`].bind(module) + +let qrc_decode: (buf: Buffer, len: number) => Buffer + +const decode = async(str: string): Promise => { + if (!str) return '' + const buf = Buffer.from(str, 'hex') + return new Promise((resolve, reject) => { + inflate(qrc_decode(buf, buf.length), (err, lrc) => { + if (err) reject(err) + else resolve(lrc.toString()) + }) + }) +} + + +const handleDecode = async(lrc: string, tlrc: string, rlrc: string) => { + if (!qrc_decode) { + // const nativeBindingPath = path.join(__dirname, '../build/Release/qrc_decode.node') + // const nativeBindingPath = isDev ? path.join(__dirname, '../build/Release/qrc_decode.node') + // eslint-disable-next-line @typescript-eslint/no-var-requires + const addon = require('qrc_decode.node') + console.log(addon) + qrc_decode = addon.qrc_decode + } + + const [lyric, tlyric, rlyric] = await Promise.all([decode(lrc), decode(tlrc), decode(rlrc)]) + return { + lyric, + tlyric, + rlyric, + } +} + + +export default () => { + mainHandle<{ lrc: string, tlrc: string, rlrc: string }, { lyric: string, tlyric: string, rlyric: string }>(WIN_MAIN_RENDERER_EVENT_NAME.handle_tx_decode_lyric, async({ params: { lrc, tlrc, rlrc } }) => { + return handleDecode(lrc, tlrc, rlrc) + }) +} diff --git a/src/renderer/utils/musicSdk/tx/index.js b/src/renderer/utils/musicSdk/tx/index.js index 6c1bd568..0c755acf 100644 --- a/src/renderer/utils/musicSdk/tx/index.js +++ b/src/renderer/utils/musicSdk/tx/index.js @@ -18,7 +18,7 @@ const tx = { }, getLyric(songInfo) { // let singer = songInfo.singer.indexOf('、') > -1 ? songInfo.singer.split('、')[0] : songInfo.singer - return lyric.getLyric(songInfo.songmid) + return lyric.getLyric(songInfo) }, getPic(songInfo) { return apis('tx').getPic(songInfo) diff --git a/src/renderer/utils/musicSdk/tx/lyric.js b/src/renderer/utils/musicSdk/tx/lyric.js index f0856b31..8b5cf6d5 100644 --- a/src/renderer/utils/musicSdk/tx/lyric.js +++ b/src/renderer/utils/musicSdk/tx/lyric.js @@ -1,23 +1,283 @@ import { httpFetch } from '../../request' -import { b64DecodeUnicode, decodeName } from '../../index' +import getMusicInfo from './musicInfo' +import { rendererInvoke } from '@common/rendererIpc' +import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames' -export default { - regexps: { - matchLrc: /.+"lyric":"([\w=+/]*)".+/, +const songIdMap = new Map() +const promises = new Map() +export const decodeLyric = (lrc, tlrc, rlrc) => rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.handle_tx_decode_lyric, { lrc, tlrc, rlrc }) + + +const parseTools = { + rxps: { + info: /^{"/, + lineTime: /^\[(\d+),\d+\]/, + wordTime: /\(\d+,\d+\)/, + wordTimeAll: /(\(\d+,\d+\))/g, + timeLabelFixRxp: /(?:\.0+|0+)$/, }, - getLyric(songmid) { - const requestObj = httpFetch(`https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=${songmid}&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&platform=yqq`, { - headers: { - Referer: 'https://y.qq.com/portal/player.html', - }, - }) - requestObj.promise = requestObj.promise.then(({ body }) => { - if (body.code != 0 || !body.lyric) return Promise.reject(new Error('Get lyric failed')) - return { - lyric: decodeName(b64DecodeUnicode(body.lyric)), - tlyric: decodeName(b64DecodeUnicode(body.trans)), + msFormat(timeMs) { + if (Number.isNaN(timeMs)) return '' + let ms = timeMs % 1000 + timeMs /= 1000 + let m = parseInt(timeMs / 60).toString().padStart(2, '0') + timeMs %= 60 + let s = parseInt(timeMs).toString().padStart(2, '0') + return `[${m}:${s}.${String(ms).padStart(3, '0')}]` + }, + parseLyric(lrc) { + lrc = lrc.trim() + lrc = lrc.replace(/\r/g, '') + if (!lrc) return { lyric: '', lxlyric: '' } + const lines = lrc.split('\n') + + const lxlrcLines = [] + const lrcLines = [] + + for (let line of lines) { + line = line.trim() + let result = this.rxps.lineTime.exec(line) + if (!result) { + if (line.startsWith('[offset')) { + lxlrcLines.push(line) + lrcLines.push(line) + } + continue } + + const startMsTime = parseInt(result[1]) + const startTimeStr = this.msFormat(startMsTime) + if (!startTimeStr) continue + + let words = line.replace(this.rxps.lineTime, '') + + lrcLines.push(`${startTimeStr}${words.replace(this.rxps.wordTimeAll, '')}`) + + let times = words.match(this.rxps.wordTimeAll) + if (!times) continue + times = times.map(time => { + const result = /\((\d+),(\d+)\)/.exec(time) + return `<${Math.max(parseInt(result[1]) - startMsTime, 0)},${result[2]}>` + }) + const wordArr = words.split(this.rxps.wordTime) + const newWords = times.map((time, index) => `${time}${wordArr[index]}`).join('') + lxlrcLines.push(`${startTimeStr}${newWords}`) + } + return { + lyric: lrcLines.join('\n'), + lxlyric: lxlrcLines.join('\n'), + } + }, + parseRlyric(lrc) { + lrc = lrc.trim() + lrc = lrc.replace(/\r/g, '') + if (!lrc) return { lyric: '', lxlyric: '' } + const lines = lrc.split('\n') + + const lrcLines = [] + + for (let line of lines) { + line = line.trim() + let result = this.rxps.lineTime.exec(line) + if (!result) continue + + const startMsTime = parseInt(result[1]) + const startTimeStr = this.msFormat(startMsTime) + if (!startTimeStr) continue + + let words = line.replace(this.rxps.lineTime, '') + + lrcLines.push(`${startTimeStr}${words.replace(this.rxps.wordTimeAll, '')}`) + } + return lrcLines.join('\n') + }, + removeTag(str) { + return str.replace(/^[\S\s]*?LyricContent="/, '').replace(/"\/>[\S\s]*?$/, '') + }, + getIntv(interval) { + let [m, s, ms] = interval.split(/:|\./) + + return parseInt(m) * 3600000 + parseInt(s) * 1000 + parseInt(ms) + }, + fixRlrcTimeTag(rlrc, lrc) { + // console.log(lrc) + // console.log(rlrc) + const rlrcLines = rlrc.split('\n') + let lrcLines = lrc.split('\n') + // let temp = [] + const timeTagRxp = /^\[([\d:.]+)\]/ + let newLrc = [] + rlrcLines.forEach((line) => { + const result = timeTagRxp.exec(line) + if (!result) return + const words = line.replace(timeTagRxp, '') + if (!words.trim()) return + const t1 = this.getIntv(result[1]) + + while (lrcLines.length) { + const lrcLine = lrcLines.shift() + const lrcLineResult = timeTagRxp.exec(lrcLine) + if (!lrcLineResult) continue + const t2 = this.getIntv(lrcLineResult[1]) + if (Math.abs(t1 - t2) < 10) { + newLrc.push(line.replace(timeTagRxp, lrcLineResult[0])) + break + } + // temp.push(line) + } + // lrcLines = [...temp, ...lrcLines] + // temp = [] }) - return requestObj + return newLrc.join('\n') + }, + fixTlrcTimeTag(tlrc, lrc) { + // console.log(lrc) + // console.log(tlrc) + const tlrcLines = tlrc.split('\n') + let lrcLines = lrc.split('\n') + // let temp = [] + const timeTagRxp = /^\[[\d:.]+\]/ + let newLrc = [] + tlrcLines.forEach((line) => { + const result = timeTagRxp.exec(line) + if (!result) return + const words = line.replace(timeTagRxp, '') + if (!words.trim()) return + const tag = result[0].replace(/\d]/, '').replace(this.rxps.timeLabelFixRxp, '') + + while (lrcLines.length) { + const lrcLine = lrcLines.shift() + const lrcLineResult = timeTagRxp.exec(lrcLine) + if (!lrcLineResult) continue + if (lrcLineResult[0].includes(tag)) { + newLrc.push(line.replace(timeTagRxp, lrcLineResult[0])) + break + } + // temp.push(line) + } + // lrcLines = [...temp, ...lrcLines] + // temp = [] + }) + return newLrc.join('\n') + }, + parse(lrc, tlrc, rlrc) { + const info = { + lyric: '', + tlyric: '', + rlyric: '', + lxlyric: '', + } + if (lrc) { + let { lyric, lxlyric } = this.parseLyric(this.removeTag(lrc)) + info.lyric = lyric + info.lxlyric = lxlyric + // console.log(lyric) + // console.log(lxlyric) + } + if (rlrc) info.rlyric = this.fixRlrcTimeTag(this.parseRlyric(this.removeTag(rlrc)), info.lyric) + if (tlrc) info.tlyric = this.fixTlrcTimeTag(tlrc, info.lyric) + // console.log(info.lyric) + // console.log(info.tlyric) + // console.log(info.rlyric) + + return info }, } + + +export default { + successCode: 0, + async getSongId({ songId, songmid }) { + if (songId) return songId + if (songIdMap.has(songmid)) return songIdMap.get(songmid) + if (promises.has(songmid)) return (await promises.get(songmid)).songId + const promise = getMusicInfo(songmid) + promises.set(promise) + const info = await promise + songIdMap.set(songmid, info.songId) + promises.delete(songmid) + return info.songId + }, + async parseLyric(lrc, tlrc, rlrc) { + const { lyric, tlyric, rlyric } = await decodeLyric(lrc, tlrc, rlrc) + // return { + + // } + // console.log(lyric) + // console.log(tlyric) + // console.log(rlyric) + return parseTools.parse(lyric, tlyric, rlyric) + }, + getLyric(mInfo, retryNum = 0) { + if (retryNum > 3) return Promise.reject(new Error('Get lyric failed')) + + return { + cancelHttp() { + + }, + promise: this.getSongId(mInfo).then(songId => { + const requestObj = httpFetch('https://u.y.qq.com/cgi-bin/musicu.fcg', { + method: 'post', + headers: { + referer: 'https://y.qq.com', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36', + }, + body: { + comm: { + ct: '19', + cv: '1859', + uin: '0', + }, + req: { + method: 'GetPlayLyricInfo', + module: 'music.musichallSong.PlayLyricInfo', + param: { + format: 'json', + crypt: 1, + ct: 19, + cv: 1873, + interval: 0, + lrc_t: 0, + qrc: 1, + qrc_t: 0, + roma: 1, + roma_t: 0, + songID: songId, + trans: 1, + trans_t: 0, + type: -1, + }, + }, + }, + }) + return requestObj.promise.then(({ body }) => { + // console.log(body) + if (body.code != this.successCode || body.req.code != this.successCode) return this.getLyric(songId, ++retryNum) + const data = body.req.data + return this.parseLyric(data.lyric, data.trans, data.roma) + }) + }), + } + }, +} + +// export default { +// regexps: { +// matchLrc: /.+"lyric":"([\w=+/]*)".+/, +// }, +// getLyric(songmid) { +// const requestObj = httpFetch(`https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=${songmid}&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&platform=yqq`, { +// headers: { +// Referer: 'https://y.qq.com/portal/player.html', +// }, +// }) +// requestObj.promise = requestObj.promise.then(({ body }) => { +// if (body.code != 0 || !body.lyric) return Promise.reject(new Error('Get lyric failed')) +// return { +// lyric: decodeName(b64DecodeUnicode(body.lyric)), +// tlyric: decodeName(b64DecodeUnicode(body.trans)), +// } +// }) +// return requestObj +// }, +// } diff --git a/src/renderer/utils/musicSdk/wy/lyric.js b/src/renderer/utils/musicSdk/wy/lyric.js index b2120b35..f052c88a 100644 --- a/src/renderer/utils/musicSdk/wy/lyric.js +++ b/src/renderer/utils/musicSdk/wy/lyric.js @@ -77,7 +77,13 @@ const parseTools = { for (let line of lines) { line = line.trim() let result = this.rxps.lineTime.exec(line) - if (!result) continue + if (!result) { + if (line.startsWith('[offset')) { + lxlrcLines.push(line) + lrcLines.push(line) + } + continue + } const startMsTime = parseInt(result[1]) const startTimeStr = this.msFormat(startMsTime)