From 4be5bcc979107373d91abbce91109abca2c85876 Mon Sep 17 00:00:00 2001 From: lyswhut Date: Sat, 5 Sep 2020 03:50:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=85=B7=E7=8B=97=E6=AD=8C?= =?UTF-8?q?=E8=AF=8D=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- publish/changeLog.md | 1 + src/renderer/utils/music/kg/lyric.js | 106 +++++++++++++++++++++++++-- src/renderer/utils/music/kg/util.js | 19 +++++ 3 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 src/renderer/utils/music/kg/util.js diff --git a/publish/changeLog.md b/publish/changeLog.md index 26c780e9..602e0ec8 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -2,6 +2,7 @@ - 在歌单详情界面新增播放当前歌单按钮、收藏歌单按钮,注:播放歌单不会将歌曲添加到试听列表 - 新增`不允许将歌词窗口拖出主屏幕之外`的设置项,默认开启,在连接多个屏幕时想要拖动到其他屏幕时可关闭此设置 +- 新增大部分平台的歌词翻译,感谢 @InoriHimea 提供的[krc解码算法](https://github.com/lyswhut/lx-music-desktop/issues/296#issuecomment-683285784) - 新增`显示歌词翻译`设置,默认开启,仅支持某些平台,注:无论该设置是否开启,嵌入或下载歌词时都不会带上翻译 - 新增`显示切换动画`设置,默认开启,关闭时将基本禁用软件内的所有切换动画 diff --git a/src/renderer/utils/music/kg/lyric.js b/src/renderer/utils/music/kg/lyric.js index a6574791..52b66612 100644 --- a/src/renderer/utils/music/kg/lyric.js +++ b/src/renderer/utils/music/kg/lyric.js @@ -1,4 +1,41 @@ import { httpFetch } from '../../request' +import { decodeLyric } from './util' + +const parseLyric = str => { + str = str.replace(/(?:<\d+,\d+,\d+>|\r)/g, '') + let trans = str.match(/\[language:([\w=\\/+]+)\]/) + let tlyric + if (trans) { + str = str.replace(/\[language:[\w=\\/+]+\]\n/, '') + let json = JSON.parse(Buffer.from(trans[1], 'base64').toString()) + for (const item of json.content) { + if (item.type == 1) { + tlyric = item.lyricContent + break + } + } + } + let i = 0 + let lyric = str.replace(/\[((\d+),\d+)\].*/g, str => { + let result = str.match(/\[((\d+),\d+)\].*/) + let time = parseInt(result[2]) + let ms = time % 1000 + time /= 1000 + let h = parseInt(time / 3600).toString().padStart(2, '0') + time %= 3600 + let m = parseInt(time / 60).toString().padStart(2, '0') + time %= 60 + let s = parseInt(time).toString().padStart(2, '0') + time = `${h}:${m}:${s}.${ms}` + if (tlyric) tlyric[i] = `[${time}]${tlyric[i++][0]}` + return str.replace(result[1], time) + }) + tlyric = tlyric ? tlyric.join('\n') : '' + return { + lyric, + tlyric, + } +} export default { getIntv(interval) { @@ -11,8 +48,30 @@ export default { } return parseInt(intv) }, - getLyric(songInfo, tryNum = 0) { - let requestObj = httpFetch(`http://m.kugou.com/app/i/krc.php?cmd=100&keyword=${encodeURIComponent(songInfo.name)}&hash=${songInfo.hash}&timelength=${songInfo._interval || this.getIntv(songInfo.interval)}&d=0.38664927426725626`, { + // getLyric(songInfo, tryNum = 0) { + // let requestObj = httpFetch(`http://m.kugou.com/app/i/krc.php?cmd=100&keyword=${encodeURIComponent(songInfo.name)}&hash=${songInfo.hash}&timelength=${songInfo._interval || this.getIntv(songInfo.interval)}&d=0.38664927426725626`, { + // headers: { + // 'KG-RC': 1, + // 'KG-THash': 'expand_search_manager.cpp:852736169:451', + // 'User-Agent': 'KuGou2012-9020-ExpandSearchManager', + // }, + // }) + // requestObj.promise = requestObj.promise.then(({ body, statusCode }) => { + // if (statusCode !== 200) { + // if (tryNum > 5) return Promise.reject('歌词获取失败') + // let tryRequestObj = this.getLyric(songInfo, ++tryNum) + // requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) + // return tryRequestObj.promise + // } + // return { + // lyric: body, + // tlyric: '', + // } + // }) + // return requestObj + // }, + searchLyric(name, hash, time, tryNum = 0) { + let requestObj = httpFetch(`http://lyrics.kugou.com/search?ver=1&man=yes&client=pc&keyword=${encodeURIComponent(name)}&hash=${hash}&timelength=${time}`, { headers: { 'KG-RC': 1, 'KG-THash': 'expand_search_manager.cpp:852736169:451', @@ -22,14 +81,49 @@ export default { requestObj.promise = requestObj.promise.then(({ body, statusCode }) => { if (statusCode !== 200) { if (tryNum > 5) return Promise.reject('歌词获取失败') - let tryRequestObj = this.getLyric(songInfo, ++tryNum) + let tryRequestObj = this.searchLyric(name, hash, time, ++tryNum) requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) return tryRequestObj.promise } - return { - lyric: body, - tlyric: '', + if (body.candidates.length) { + let info = body.candidates[0] + return { id: info.id, accessKey: info.accesskey } } + return null + }) + return requestObj + }, + getLyricDownload(id, accessKey, tryNum = 0) { + let requestObj = httpFetch(`http://lyrics.kugou.com/download?ver=1&client=pc&id=${id}&accesskey=${accessKey}&fmt=krc&charset=utf8`, { + headers: { + 'KG-RC': 1, + 'KG-THash': 'expand_search_manager.cpp:852736169:451', + 'User-Agent': 'KuGou2012-9020-ExpandSearchManager', + }, + }) + requestObj.promise = requestObj.promise.then(({ body, statusCode }) => { + if (statusCode !== 200) { + if (tryNum > 5) return Promise.reject('歌词获取失败') + let tryRequestObj = this.getLyric(id, accessKey, ++tryNum) + requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) + return tryRequestObj.promise + } + + return decodeLyric(body.content).then(result => parseLyric(result)) + }) + return requestObj + }, + getLyric(songInfo, tryNum = 0) { + let requestObj = this.searchLyric(songInfo.name, songInfo.hash, songInfo._interval || this.getIntv(songInfo.interval)) + + requestObj.promise = requestObj.promise.then(result => { + if (!result) return { lyric: '', tlyric: '' } + + let requestObj2 = this.getLyricDownload(result.id, result.accessKey) + + requestObj.cancelHttp = requestObj2.cancelHttp.bind(requestObj2) + + return requestObj2.promise }) return requestObj }, diff --git a/src/renderer/utils/music/kg/util.js b/src/renderer/utils/music/kg/util.js new file mode 100644 index 00000000..73b5e743 --- /dev/null +++ b/src/renderer/utils/music/kg/util.js @@ -0,0 +1,19 @@ +import { inflate } from 'zlib' + +// https://github.com/lyswhut/lx-music-desktop/issues/296#issuecomment-683285784 +const enc_key = Buffer.from([0x40, 0x47, 0x61, 0x77, 0x5e, 0x32, 0x74, 0x47, 0x51, 0x36, 0x31, 0x2d, 0xce, 0xd2, 0x6e, 0x69], 'binary') +export const decodeLyric = str => new Promise((resolve, reject) => { + if (!str.length) return + const buf_str = Buffer.from(str, 'base64').slice(4) + for (let i = 0, len = buf_str.length; i < len; i++) { + buf_str[i] = buf_str[i] ^ enc_key[i % 16] + } + inflate(buf_str, (err, result) => { + if (err) return reject(err) + resolve(result.toString()) + }) +}) + +// s.content[0].lyricContent.forEach(([str]) => { +// console.log(str) +// })