mirror of
https://github.com/MeoProject/lx-music-api-server.git
synced 2025-05-23 19:17:41 +08:00
feat: QQ音乐歌词功能
This commit is contained in:
parent
40943f354e
commit
d4056fcb7b
@ -60,7 +60,7 @@ default = {
|
|||||||
"proxy": {
|
"proxy": {
|
||||||
"enable": False,
|
"enable": False,
|
||||||
"http_addr": "http://127.0.0.1:7890",
|
"http_addr": "http://127.0.0.1:7890",
|
||||||
"https_addr": "https://127.0.0.1:7890",
|
"https_addr": "http://127.0.0.1:7890",
|
||||||
},
|
},
|
||||||
"_proxy-desc": "代理配置,HTTP与HTTPS协议需分开配置",
|
"_proxy-desc": "代理配置,HTTP与HTTPS协议需分开配置",
|
||||||
"log_file": True,
|
"log_file": True,
|
||||||
|
10
common/natives/__init__.py
Normal file
10
common/natives/__init__.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# ----------------------------------------
|
||||||
|
# - mode: python -
|
||||||
|
# - author: helloplhm-qwq -
|
||||||
|
# - name: __init__.py -
|
||||||
|
# - project: lx-music-api-server -
|
||||||
|
# - license: MIT -
|
||||||
|
# ----------------------------------------
|
||||||
|
# This file is part of the "lx-music-api-server" project.
|
||||||
|
|
||||||
|
from . import qdes
|
32
common/qdes.py
Normal file
32
common/qdes.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# ----------------------------------------
|
||||||
|
# - mode: python -
|
||||||
|
# - author: helloplhm-qwq -
|
||||||
|
# - name: qdes.py -
|
||||||
|
# - project: lx-music-api-server -
|
||||||
|
# - license: MIT -
|
||||||
|
# ----------------------------------------
|
||||||
|
# This file is part of the "lx-music-api-server" project.
|
||||||
|
|
||||||
|
from .log import log
|
||||||
|
from . import variable
|
||||||
|
import binascii
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
logger = log('qdes')
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .natives import qdes
|
||||||
|
variable.qdes_lib_loaded = True
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
import qdes
|
||||||
|
variable.qdes_lib_loaded = True
|
||||||
|
except:
|
||||||
|
logger.warning('QRC解密库qdes加载失败, 可能为不支持当前系统, QRC相关的逐字歌词获取将无法使用')
|
||||||
|
|
||||||
|
def qdes_decrypt(qrc):
|
||||||
|
if variable.qdes_lib_loaded:
|
||||||
|
decoded = zlib.decompress(qdes.LyricDecode(binascii.unhexlify(qrc.encode('utf-8')))).decode('utf-8')
|
||||||
|
return decoded
|
||||||
|
else:
|
||||||
|
raise ModuleNotFoundError('qdes解密库未被加载')
|
@ -7,13 +7,13 @@
|
|||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
# This file is part of the "lx-music-api-server" project.
|
# This file is part of the "lx-music-api-server" project.
|
||||||
|
|
||||||
import os
|
import os as _os
|
||||||
import ujson as json
|
import ujson as _json
|
||||||
|
|
||||||
def _read_config_file():
|
def _read_config_file():
|
||||||
try:
|
try:
|
||||||
with open("./config.json", "r", encoding = "utf-8") as f:
|
with open("./config.json", "r", encoding = "utf-8") as f:
|
||||||
return json.load(f)
|
return _json.load(f)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -42,8 +42,9 @@ log_length_limit = log_length_limit if (log_length_limit := _read_config("common
|
|||||||
log_file = log_file if (not isinstance(log_file := _read_config("common.log_file"), type(None))) else True
|
log_file = log_file if (not isinstance(log_file := _read_config("common.log_file"), type(None))) else True
|
||||||
running = True
|
running = True
|
||||||
config = {}
|
config = {}
|
||||||
workdir = os.getcwd()
|
workdir = _os.getcwd()
|
||||||
banList_suggest = 0
|
banList_suggest = 0
|
||||||
iscn = True
|
iscn = True
|
||||||
fake_ip = None
|
fake_ip = None
|
||||||
aioSession = None
|
aioSession = None
|
||||||
|
qdes_lib_loaded = False
|
5
main.py
5
main.py
@ -97,7 +97,10 @@ async def handle(request):
|
|||||||
return handleResult({"code": 1, "msg": "lxm请求头验证失败", "data": None}, 403)
|
return handleResult({"code": 1, "msg": "lxm请求头验证失败", "data": None}, 403)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return handleResult(await getattr(modules, method)(source, songId, quality))
|
if (method in dir(modules)):
|
||||||
|
return handleResult(await getattr(modules, method)(source, songId, quality))
|
||||||
|
else:
|
||||||
|
return handleResult(await modules.other(method, source, songId, quality))
|
||||||
except:
|
except:
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
return handleResult({'code': 4, 'msg': '内部服务器错误', 'data': None}, 500)
|
return handleResult({'code': 4, 'msg': '内部服务器错误', 'data': None}, 500)
|
||||||
|
@ -123,9 +123,9 @@ async def url(source, songId, quality):
|
|||||||
'data': None,
|
'data': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
async def info(source, songid, _):
|
async def other(method, source, songid, _):
|
||||||
try:
|
try:
|
||||||
func = require('modules.' + source + '.info')
|
func = require('modules.' + source + '.' + method)
|
||||||
except:
|
except:
|
||||||
return {
|
return {
|
||||||
'code': 1,
|
'code': 1,
|
||||||
@ -145,25 +145,3 @@ async def info(source, songid, _):
|
|||||||
'msg': e.args[0],
|
'msg': e.args[0],
|
||||||
'data': None,
|
'data': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
async def mv(source, mvId, _):
|
|
||||||
try:
|
|
||||||
func = require('modules.' + source + '.mv')
|
|
||||||
except:
|
|
||||||
return {
|
|
||||||
'code': 1,
|
|
||||||
'msg': '未知的源或不支持的方法',
|
|
||||||
'data': None,
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
result = await func(mvId)
|
|
||||||
return {
|
|
||||||
'code': 0,
|
|
||||||
'msg': 'success',
|
|
||||||
'data': result
|
|
||||||
}
|
|
||||||
except FailedException as e:
|
|
||||||
return {
|
|
||||||
'code': 2,
|
|
||||||
'msg': e.args[0],
|
|
||||||
}
|
|
@ -10,6 +10,7 @@
|
|||||||
from .player import url
|
from .player import url
|
||||||
from .musicInfo import getMusicInfo as _getInfo
|
from .musicInfo import getMusicInfo as _getInfo
|
||||||
from .utils import formatSinger
|
from .utils import formatSinger
|
||||||
|
from .lyric import getLyric as _getLyric
|
||||||
from common import utils
|
from common import utils
|
||||||
|
|
||||||
|
|
||||||
@ -75,5 +76,6 @@ async def info(songid):
|
|||||||
'bpm': req['track_info']['bpm'],
|
'bpm': req['track_info']['bpm'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def lyric(songId):
|
||||||
|
return await _getLyric(songId)
|
||||||
|
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
|
# ----------------------------------------
|
||||||
|
# - mode: python -
|
||||||
|
# - author: helloplhm-qwq -
|
||||||
|
# - name: lyric.py -
|
||||||
|
# - project: lx-music-api-server -
|
||||||
|
# - license: MIT -
|
||||||
|
# ----------------------------------------
|
||||||
|
# This file is part of the "lx-music-api-server" project.
|
||||||
|
|
||||||
|
from .utils import signRequest
|
||||||
|
from .musicInfo import getMusicInfo
|
||||||
|
from common.exceptions import FailedException
|
||||||
|
from common.utils import createBase64Decode
|
||||||
|
from common import variable
|
||||||
|
from common import qdes
|
||||||
import re
|
import re
|
||||||
|
|
||||||
class ParseTools:
|
class ParseTools:
|
||||||
@ -130,26 +145,26 @@ class ParseTools:
|
|||||||
tlrc_lines = tlrc.split('\n')
|
tlrc_lines = tlrc.split('\n')
|
||||||
lrc_lines = lrc.split('\n')
|
lrc_lines = lrc.split('\n')
|
||||||
new_lrc = []
|
new_lrc = []
|
||||||
|
time_tag_rxp = r'^\[[\d:.]+\]'
|
||||||
|
|
||||||
for line in tlrc_lines:
|
for line in tlrc_lines:
|
||||||
result = self.rxps['lineTime2'].search(line)
|
result = re.match(time_tag_rxp, line)
|
||||||
if not result:
|
if not result:
|
||||||
continue
|
continue
|
||||||
words = re.sub(self.rxps['lineTime2'], '', line)
|
words = re.sub(time_tag_rxp, '', line)
|
||||||
if not words.strip():
|
if not words.strip():
|
||||||
continue
|
continue
|
||||||
time = result.group(1)
|
tag = re.sub(r'\[\d+:\d+\.\d+\]', '', result.group(0))
|
||||||
if '.' in time:
|
|
||||||
time += ''.ljust(3 - len(time.split('.')[1]), '0')
|
|
||||||
t1 = self.get_intv(time)
|
|
||||||
while lrc_lines:
|
while lrc_lines:
|
||||||
lrc_line = lrc_lines.pop(0)
|
lrc_line = lrc_lines.pop(0)
|
||||||
lrc_line_result = self.rxps['lineTime2'].search(lrc_line)
|
lrc_line_result = re.match(time_tag_rxp, lrc_line)
|
||||||
if not lrc_line_result:
|
if not lrc_line_result:
|
||||||
continue
|
continue
|
||||||
t2 = self.get_intv(lrc_line_result.group(1))
|
if tag in lrc_line_result.group(0):
|
||||||
if abs(t1 - t2) < 100:
|
new_lrc.append(re.sub(time_tag_rxp, lrc_line_result.group(0), line))
|
||||||
new_lrc.append(re.sub(self.rxps['lineTime2'], lrc_line_result.group(0), line))
|
|
||||||
break
|
break
|
||||||
|
|
||||||
return '\n'.join(new_lrc)
|
return '\n'.join(new_lrc)
|
||||||
|
|
||||||
def parse(self, lrc, tlrc, rlrc):
|
def parse(self, lrc, tlrc, rlrc):
|
||||||
@ -169,3 +184,74 @@ class ParseTools:
|
|||||||
info['tlyric'] = self.fix_tlrc_time_tag(tlrc, info['lyric'])
|
info['tlyric'] = self.fix_tlrc_time_tag(tlrc, info['lyric'])
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
global_parser = ParseTools()
|
||||||
|
|
||||||
|
def parseLyric(l, t = '', r = ''):
|
||||||
|
return global_parser.parse(l, t, r)
|
||||||
|
|
||||||
|
async def getLyric(songId):
|
||||||
|
# mid and Numberid
|
||||||
|
if (re.match("^[0-9]+$", str(songId))):
|
||||||
|
songId = int(songId)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
getNumberIDRequest = await getMusicInfo(songId)
|
||||||
|
except:
|
||||||
|
raise FailedException('歌曲信息获取失败')
|
||||||
|
songId = getNumberIDRequest['track_info']['id']
|
||||||
|
req = await signRequest({
|
||||||
|
"comm": {
|
||||||
|
"ct": '19',
|
||||||
|
"cv": '1859',
|
||||||
|
"uin": '0',
|
||||||
|
},
|
||||||
|
"req": {
|
||||||
|
"method": 'GetPlayLyricInfo',
|
||||||
|
"module": 'music.musichallSong.PlayLyricInfo',
|
||||||
|
"param": {
|
||||||
|
"format": 'json',
|
||||||
|
"crypt": 1 if variable.qdes_lib_loaded else 0,
|
||||||
|
"ct": 19,
|
||||||
|
"cv": 1873,
|
||||||
|
"interval": 0,
|
||||||
|
"lrc_t": 0,
|
||||||
|
"qrc": 1 if variable.qdes_lib_loaded else 0,
|
||||||
|
"qrc_t": 0,
|
||||||
|
"roma": 1 if variable.qdes_lib_loaded else 0,
|
||||||
|
"roma_t": 0,
|
||||||
|
"songID": songId,
|
||||||
|
"trans": 1,
|
||||||
|
"trans_t": 0,
|
||||||
|
"type": -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
body = req.json()
|
||||||
|
if ((body['code'] != 0) or (body['req']['code'] != 0)):
|
||||||
|
raise FailedException('歌词获取失败')
|
||||||
|
if (variable.qdes_lib_loaded):
|
||||||
|
l = body['req']['data']['lyric']
|
||||||
|
t = body['req']['data']['trans']
|
||||||
|
r = body['req']['data']['roma']
|
||||||
|
if (l.startswith('789C') and len(l) < 200): # unsupported format
|
||||||
|
raise FailedException('纯音乐短歌词不受支持')
|
||||||
|
dl = qdes.qdes_decrypt(l)
|
||||||
|
if (t):
|
||||||
|
dt = qdes.qdes_decrypt(t)
|
||||||
|
else:
|
||||||
|
dt = ''
|
||||||
|
if (r):
|
||||||
|
dr = qdes.qdes_decrypt(r)
|
||||||
|
else:
|
||||||
|
dr = ''
|
||||||
|
return global_parser.parse(dl, dt, dr)
|
||||||
|
else: # 不获取QRC时的歌词不被加密,解码base64,不进行parse,不支持逐字和罗马音,歌词数据没有毫秒
|
||||||
|
l = body['req']['data']['lyric']
|
||||||
|
t = body['req']['data']['trans']
|
||||||
|
return {
|
||||||
|
'lyric': createBase64Decode(l).decode('utf-8'),
|
||||||
|
'tlyric': createBase64Decode(t).decode('utf-8'),
|
||||||
|
'rlyric': '',
|
||||||
|
'lxlyric': '',
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user