添加x源逐字歌词的支持

pull/1211/head
lyswhut 2023-02-04 18:38:06 +08:00
parent 62330571a3
commit ab1d18debe
19 changed files with 377 additions and 30 deletions

View File

@ -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')

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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'),

4
package-lock.json generated
View File

@ -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": {

View File

@ -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": {

View File

@ -3,7 +3,7 @@
- 新增桌面歌词设置字体加粗设置,可以到设置-桌面歌词设置-加粗字体修改
- 新增是否自动下载更新设置,默认开启,可以去设置-软件更新更改
- 新增当前版本更新日志显示弹窗(建议大家阅读更新日志以了解当前版本的变化),在更新版本后将自动弹出
- 添加wy源逐字歌词的支持
- 添加wy、tx源逐字歌词的支持
### 优化

View File

@ -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',

View File

@ -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()

View File

@ -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<string> => {
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)
})
}

View File

@ -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)

View File

@ -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
// },
// }

View File

@ -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)