mirror of
https://github.com/xhongc/music-tag-web.git
synced 2026-02-02 17:59:07 +08:00
音乐标签web
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,4 +16,5 @@ yarn-error.log*
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
local_settings.py
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
35
applications/task/serialziers.py
Normal file
35
applications/task/serialziers.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class FileListSerializer(serializers.Serializer):
|
||||
file_path = serializers.CharField(required=True)
|
||||
|
||||
|
||||
class Id3Serializer(serializers.Serializer):
|
||||
file_path = serializers.CharField(required=True)
|
||||
file_name = serializers.CharField(required=True)
|
||||
|
||||
|
||||
class MusicId3Serializer(serializers.Serializer):
|
||||
title = serializers.CharField(required=True, allow_null=True, allow_blank=True)
|
||||
artist = serializers.CharField(required=True, allow_null=True, allow_blank=True)
|
||||
album = serializers.CharField(required=True, allow_null=True, allow_blank=True)
|
||||
genre = serializers.CharField(required=True, allow_null=True, allow_blank=True)
|
||||
year = serializers.CharField(required=True, allow_null=True, allow_blank=True)
|
||||
lyrics = serializers.CharField(required=True, allow_null=True, allow_blank=True)
|
||||
comment = serializers.CharField(required=True, allow_null=True, allow_blank=True)
|
||||
album_img = serializers.CharField(required=False, allow_null=True, allow_blank=True)
|
||||
|
||||
file_full_path = serializers.CharField(required=True)
|
||||
|
||||
|
||||
class UpdateId3Serializer(serializers.Serializer):
|
||||
music_id3_info = MusicId3Serializer(many=True)
|
||||
|
||||
|
||||
class FetchId3ByTitleSerializer(serializers.Serializer):
|
||||
title = serializers.CharField(required=True)
|
||||
|
||||
|
||||
class FetchLlyricSerializer(serializers.Serializer):
|
||||
song_id = serializers.CharField(required=True)
|
||||
10
applications/task/utils.py
Normal file
10
applications/task/utils.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# coding:UTF-8
|
||||
import time
|
||||
|
||||
|
||||
def timestamp_to_dt(timestamp, format_type="%Y-%m-%d %H:%M:%S"):
|
||||
# 转换成localtime
|
||||
time_local = time.localtime(timestamp)
|
||||
# 转换成新的时间格式(2016-05-05 20:28:54)
|
||||
dt = time.strftime(format_type, time_local)
|
||||
return dt
|
||||
@@ -1,9 +1,142 @@
|
||||
from rest_framework import mixins
|
||||
from rest_framework.response import Response
|
||||
import base64
|
||||
import os
|
||||
|
||||
import music_tag
|
||||
from rest_framework.decorators import action
|
||||
|
||||
from applications.task.serialziers import FileListSerializer, Id3Serializer, UpdateId3Serializer, \
|
||||
FetchId3ByTitleSerializer, FetchLlyricSerializer
|
||||
from applications.task.utils import timestamp_to_dt
|
||||
from applications.utils.send import send
|
||||
from component.drf.viewsets import GenericViewSet
|
||||
from django_vue_cli.settings import BASE_URL
|
||||
|
||||
|
||||
class TaskViewSets(mixins.ListModelMixin, GenericViewSet):
|
||||
def list(self, request, *args, **kwargs):
|
||||
return Response({"name": ":"})
|
||||
class TaskViewSets(GenericViewSet):
|
||||
def get_serializer_class(self):
|
||||
if self.action == "file_list":
|
||||
return FileListSerializer
|
||||
elif self.action == "music_id3":
|
||||
return Id3Serializer
|
||||
elif self.action == "update_id3":
|
||||
return UpdateId3Serializer
|
||||
elif self.action == "fetch_id3_by_title":
|
||||
return FetchId3ByTitleSerializer
|
||||
elif self.action == "fetch_lyric":
|
||||
return FetchLlyricSerializer
|
||||
return FileListSerializer
|
||||
|
||||
@action(methods=['POST'], detail=False)
|
||||
def file_list(self, request, *args, **kwargs):
|
||||
validate_data = self.is_validated_data(request.data)
|
||||
file_path = validate_data['file_path']
|
||||
file_path_list = file_path.split('/')
|
||||
data = os.listdir(file_path)
|
||||
children_data = []
|
||||
allow_type = ["flac", "mp3"]
|
||||
for each in data:
|
||||
file_type = each.split(".")[-1]
|
||||
if file_type not in allow_type:
|
||||
continue
|
||||
children_data.append({
|
||||
"name": each,
|
||||
"title": each
|
||||
})
|
||||
res_data = [
|
||||
{
|
||||
"name": file_path_list[-1],
|
||||
"title": file_path_list[-1],
|
||||
"expanded": True,
|
||||
"id": 1,
|
||||
"children": children_data
|
||||
}
|
||||
]
|
||||
return self.success_response(data=res_data)
|
||||
|
||||
@action(methods=['POST'], detail=False)
|
||||
def music_id3(self, request, *args, **kwargs):
|
||||
validate_data = self.is_validated_data(request.data)
|
||||
file_path = validate_data['file_path']
|
||||
file_name = validate_data['file_name']
|
||||
file_title = file_name.split('.')[0]
|
||||
f = music_tag.load_file(f"{file_path}/{file_name}")
|
||||
artwork = f["artwork"].values
|
||||
bs64_img = ""
|
||||
if artwork:
|
||||
bs64_img = base64.b64encode(artwork[0].raw).decode()
|
||||
|
||||
res_data = {
|
||||
"title": f["title"].value or file_title,
|
||||
"artist": f["artist"].value,
|
||||
"album": f["album"].value,
|
||||
"genre": f["genre"].value,
|
||||
"year": f["year"].value,
|
||||
"lyrics": f["lyrics"].value,
|
||||
"comment": f["comment"].value,
|
||||
"artwork": "data:image/jpeg;base64," + bs64_img,
|
||||
}
|
||||
return self.success_response(data=res_data)
|
||||
|
||||
@action(methods=['POST'], detail=False)
|
||||
def update_id3(self, request, *args, **kwargs):
|
||||
validate_data = self.is_validated_data(request.data)
|
||||
music_id3_info = validate_data['music_id3_info']
|
||||
for each in music_id3_info:
|
||||
f = music_tag.load_file(each["file_full_path"])
|
||||
f["title"] = each["title"]
|
||||
f["artist"] = each["artist"]
|
||||
f["album"] = each["album"]
|
||||
f["genre"] = each["genre"]
|
||||
f["year"] = each["year"]
|
||||
f["lyrics"] = each["lyrics"]
|
||||
f["comment"] = each["comment"]
|
||||
if each.get("album_img", None):
|
||||
img_data = send().GET(each["album_img"])
|
||||
if img_data.status_code == 200:
|
||||
f['artwork'] = img_data.content
|
||||
f['artwork'].first.raw_thumbnail([64, 64])
|
||||
f.save()
|
||||
return self.success_response()
|
||||
|
||||
@action(methods=['POST'], detail=False)
|
||||
def fetch_lyric(self, request, *args, **kwargs):
|
||||
validate_data = self.is_validated_data(request.data)
|
||||
song_id = validate_data["song_id"]
|
||||
try:
|
||||
data = send({"url": BASE_URL + "api/song/lyric?lv=-1&kv=-1&tv=-1",
|
||||
"params": {"id": song_id}}, "linuxapi").POST("")
|
||||
lyric = data.json().get("lrc", {}).get("lyric")
|
||||
except Exception as e:
|
||||
lyric = f"未找到歌词 {e}"
|
||||
return self.success_response(data=lyric)
|
||||
|
||||
@action(methods=['POST'], detail=False)
|
||||
def fetch_id3_by_title(self, request, *args, **kwargs):
|
||||
validate_data = self.is_validated_data(request.data)
|
||||
title = validate_data["title"]
|
||||
data = send({'s': title, 'type': '1', 'limit': '10', 'offset': '0'}).POST("weapi/cloudsearch/get/web")
|
||||
songs = data.json().get("result", {}).get("songs", [])
|
||||
formated_songs = []
|
||||
for song in songs:
|
||||
artists = song.get("ar", [])
|
||||
album = song.get("al", {})
|
||||
if artists:
|
||||
artist = artists[0].get("name", "")
|
||||
artist_id = artists[0].get("id", "")
|
||||
else:
|
||||
artist = ""
|
||||
artist_id = ""
|
||||
year = song.get("publishTime", 0)
|
||||
if year:
|
||||
year = timestamp_to_dt(year / 1000, "%Y")
|
||||
formated_songs.append({
|
||||
"id": song["id"],
|
||||
"name": song["name"],
|
||||
"artist": artist,
|
||||
"artist_id": artist_id,
|
||||
"album": album.get("name", ""),
|
||||
"album_id": album.get("id", ""),
|
||||
"album_img": album.get("picUrl", {}),
|
||||
"year": year
|
||||
})
|
||||
return self.success_response(data=formated_songs)
|
||||
|
||||
0
applications/utils/__init__.py
Normal file
0
applications/utils/__init__.py
Normal file
79
applications/utils/encrypt.py
Normal file
79
applications/utils/encrypt.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from json import dumps
|
||||
from os import urandom
|
||||
from base64 import b64encode
|
||||
from binascii import hexlify
|
||||
from hashlib import md5
|
||||
|
||||
from Cryptodome.Cipher import AES
|
||||
|
||||
__all__ = ["weEncrypt", "linuxEncrypt", "eEncrypt", "MD5"]
|
||||
|
||||
MODULUS = (
|
||||
"00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7"
|
||||
"b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280"
|
||||
"104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932"
|
||||
"575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b"
|
||||
"3ece0462db0a22b8e7"
|
||||
)
|
||||
PUBKEY = "010001"
|
||||
NONCE = b"0CoJUm6Qyw8W8jud"
|
||||
LINUXKEY = b"rFgB&h#%2?^eDg:Q"
|
||||
EAPIKEY = b'e82ckenh8dichen8'
|
||||
|
||||
|
||||
def MD5(value):
|
||||
m = md5()
|
||||
m.update(value.encode())
|
||||
return m.hexdigest()
|
||||
|
||||
|
||||
def weEncrypt(text):
|
||||
"""
|
||||
引用自 https://github.com/darknessomi/musicbox/blob/master/NEMbox/encrypt.py#L40
|
||||
"""
|
||||
data = dumps(text).encode("utf-8")
|
||||
secret = create_key(16)
|
||||
method = {"iv": True, "base64": True}
|
||||
params = aes(aes(data, NONCE, method), secret, method)
|
||||
encseckey = rsa(secret, PUBKEY, MODULUS)
|
||||
return {"params": params, "encSecKey": encseckey}
|
||||
|
||||
|
||||
def linuxEncrypt(text):
|
||||
"""
|
||||
参考自 https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/util/crypto.js#L28
|
||||
"""
|
||||
text = str(text).encode()
|
||||
data = aes(text, LINUXKEY)
|
||||
return {"eparams": data.decode()}
|
||||
|
||||
|
||||
def eEncrypt(url, text):
|
||||
text = str(text)
|
||||
digest = MD5("nobody{}use{}md5forencrypt".format(url, text))
|
||||
data = "{}-36cd479b6b5-{}-36cd479b6b5-{}".format(url, text, digest)
|
||||
return {"params": aes(data.encode(), EAPIKEY)}
|
||||
|
||||
|
||||
def aes(text, key, method={}):
|
||||
pad = 16 - len(text) % 16
|
||||
text = text + bytearray([pad] * pad)
|
||||
if "iv" in method:
|
||||
encryptor = AES.new(key, AES.MODE_CBC, b"0102030405060708")
|
||||
else:
|
||||
encryptor = AES.new(key, AES.MODE_ECB)
|
||||
ciphertext = encryptor.encrypt(text)
|
||||
if "base64" in method:
|
||||
return b64encode(ciphertext)
|
||||
return hexlify(ciphertext).upper()
|
||||
|
||||
|
||||
def rsa(text, pubkey, modulus):
|
||||
text = text[::-1]
|
||||
rs = pow(int(hexlify(text), 16),
|
||||
int(pubkey, 16), int(modulus, 16))
|
||||
return format(rs, "x").zfill(256)
|
||||
|
||||
|
||||
def create_key(size):
|
||||
return hexlify(urandom(size))[:16]
|
||||
74
applications/utils/public.py
Normal file
74
applications/utils/public.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from json import loads
|
||||
from django.http import HttpResponse
|
||||
|
||||
BASE_URL = "https://music.163.com/"
|
||||
|
||||
|
||||
def readFile(method, path, mode="r"):
|
||||
with open(path, mode) as f:
|
||||
if method == "read":
|
||||
return f.read()
|
||||
elif method == "readlines":
|
||||
return f.readlines()
|
||||
|
||||
|
||||
def saveFile(path, content, mode="w"):
|
||||
with open(path, mode) as f:
|
||||
f.write(str(content))
|
||||
|
||||
|
||||
def getCookie():
|
||||
return loads(
|
||||
readFile("read", "cookies").replace("'", '"').encode()
|
||||
)
|
||||
|
||||
|
||||
def request_query(r, *args):
|
||||
# ["id",{"ids":800435}] ["id","ids"] "id"
|
||||
def check(txt):
|
||||
if type(txt) == int:
|
||||
return str(txt)
|
||||
return txt
|
||||
dic = {}
|
||||
try:
|
||||
info = loads(r.body)
|
||||
except:
|
||||
pass
|
||||
for i in args:
|
||||
if type(i) == list:
|
||||
j = i[1]
|
||||
i = i[0]
|
||||
else:
|
||||
j = i
|
||||
if r.method == "POST":
|
||||
try:
|
||||
query = info[i] if r.body[0] == 123 else r.POST.get(i)
|
||||
except:
|
||||
query = None
|
||||
elif r.method == "GET":
|
||||
query = r.GET.get(i)
|
||||
try:
|
||||
if type(j) == dict:
|
||||
key = list(j.keys())[0]
|
||||
dic[key] = check(query if query else j[key])
|
||||
else:
|
||||
dic[j] = check(query)
|
||||
except:
|
||||
dic[i] = check(query)
|
||||
return dic
|
||||
|
||||
|
||||
def Http_Response(r, text, type="application/json,charset=UTF-8"):
|
||||
try:
|
||||
query = request_query(r, "var", "cb")
|
||||
except:
|
||||
query = {"var": None, "cb": None}
|
||||
if query["var"] or query["cb"]:
|
||||
if query["var"]:
|
||||
text = "{}={}".format(query["var"], text)
|
||||
elif query["cb"]:
|
||||
text = "{}({})".format(query["cb"], text)
|
||||
type = "application/javascript; charset=UTF-8"
|
||||
if type == "":
|
||||
return HttpResponse(text)
|
||||
return HttpResponse(text, content_type=type)
|
||||
101
applications/utils/send.py
Normal file
101
applications/utils/send.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import requests
|
||||
from time import time
|
||||
from json import loads
|
||||
from random import randint
|
||||
from .public import readFile, getCookie
|
||||
from .encrypt import weEncrypt, linuxEncrypt, eEncrypt
|
||||
|
||||
|
||||
userAgents = [
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36',
|
||||
'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36',
|
||||
'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36',
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Mobile/14F89;GameHelper',
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1',
|
||||
'Mozilla/5.0 (iPad; CPU OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1',
|
||||
'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; BKK-AL10 Build/HONORBKK-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/10.6 Mobile Safari/537.36',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:46.0) Gecko/20100101 Firefox/46.0',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:46.0) Gecko/20100101 Firefox/46.0',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/13.10586'
|
||||
]
|
||||
|
||||
|
||||
class send:
|
||||
def __init__(self, data={}, encrypt_method="weapi", timeout=10, url=""):
|
||||
self.BASE_URL = "https://music.163.com/"
|
||||
self.headers = {
|
||||
"User-Agent": userAgents[randint(0, len(userAgents)) - 1],
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Referer": self.BASE_URL
|
||||
}
|
||||
self.session = requests.session()
|
||||
self.encrypt_method = encrypt_method
|
||||
self.data = data
|
||||
self.timeout = timeout
|
||||
self.url = url
|
||||
|
||||
def __url(self, url):
|
||||
if url == "":
|
||||
return self.BASE_URL+"api/linux/forward"
|
||||
if url[:4] == "http":
|
||||
return url
|
||||
return self.BASE_URL + url
|
||||
|
||||
def __cookies(self, data={}):
|
||||
try:
|
||||
cookies = getCookie()
|
||||
except:
|
||||
return data
|
||||
return {**data, **cookies}
|
||||
|
||||
def encrypt(self, data):
|
||||
cookie = self.__cookies()
|
||||
if self.encrypt_method == "linuxapi":
|
||||
self.headers["User-Agent"] = userAgents[0]
|
||||
data["method"] = "POST"
|
||||
return linuxEncrypt(data)
|
||||
elif self.encrypt_method == "weapi":
|
||||
data["csrf_token"] = cookie["__csrf"] if "__csrf" in cookie else ""
|
||||
return weEncrypt(data)
|
||||
elif self.encrypt_method == "eapi":
|
||||
data["header"] = {
|
||||
'osver': "",
|
||||
"appver": "8.0.0",
|
||||
"channel": "",
|
||||
"deviceId": "",
|
||||
"mobilename": "",
|
||||
"os": "android",
|
||||
"resolution": "1920x1080",
|
||||
"versioncode": "140",
|
||||
"buildver": str(int(time())),
|
||||
"requestId": str(int(time()*100))+"_0"+str(randint(100, 999)),
|
||||
"__csrf": cookie["__csrf"] if "__csrf" in cookie else ""
|
||||
}
|
||||
if "MUSIC_U" in cookie: data["header"]["MUSIC_U"] = cookie["MUSIC_U"]
|
||||
if "MUSIC_A" in cookie: data["header"]["MUSIC_A"] = cookie["MUSIC_A"]
|
||||
self.headers["Cookie"] = ''.join(map(lambda key: f"{key}={data['header'][key]};",data["header"]))
|
||||
return eEncrypt(self.url, data)
|
||||
return data
|
||||
|
||||
def POST(self, url, cookie={}):
|
||||
response = self.session.post(self.__url(url),
|
||||
data=self.encrypt(self.data),
|
||||
headers=self.headers,
|
||||
cookies=self.__cookies(cookie),
|
||||
timeout=self.timeout)
|
||||
return response
|
||||
|
||||
def GET(self, url, cookie={}):
|
||||
if self.encrypt_method == "eapi":
|
||||
self.headers["User-Agent"] = userAgents[-1]
|
||||
response = self.session.get(self.__url(url),
|
||||
headers=self.headers,
|
||||
cookies=self.__cookies(cookie),
|
||||
timeout=self.timeout)
|
||||
return response
|
||||
@@ -10,42 +10,37 @@ class ApiGenericMixin(object):
|
||||
"""API视图类通用函数"""
|
||||
|
||||
# TODO 权限部分加载基类中
|
||||
# permission_classes = ()
|
||||
permission_classes = ()
|
||||
|
||||
def finalize_response(self, request, response, *args, **kwargs):
|
||||
"""统一数据返回格式"""
|
||||
# 文件导出时response {HttpResponse}
|
||||
if not isinstance(response, Response):
|
||||
return response
|
||||
"""统一返回数据格式"""
|
||||
|
||||
if response.status_code == 403:
|
||||
pass
|
||||
if response.data is None:
|
||||
response.data = {"result": True, "code": ResponseCodeStatus.OK, "message": "success", "data": []}
|
||||
response.data = {
|
||||
'result': True,
|
||||
'message': 'success',
|
||||
'data': None
|
||||
}
|
||||
elif isinstance(response.data, (list, tuple)):
|
||||
response.data = {
|
||||
"result": True,
|
||||
"code": ResponseCodeStatus.OK,
|
||||
"message": "success",
|
||||
"data": response.data,
|
||||
'result': True,
|
||||
'message': 'success',
|
||||
'data': response.data
|
||||
}
|
||||
elif isinstance(response.data, dict):
|
||||
if not ("result" in response.data):
|
||||
response.data = {
|
||||
"result": True,
|
||||
"code": ResponseCodeStatus.OK,
|
||||
"message": "success",
|
||||
"data": response.data,
|
||||
}
|
||||
else:
|
||||
response.data = {
|
||||
"result": response.data["result"],
|
||||
"code": ResponseCodeStatus.OK,
|
||||
"message": response.data.get("message"),
|
||||
"data": response.data,
|
||||
}
|
||||
if response.status_code == status.HTTP_204_NO_CONTENT and request.method == "DELETE":
|
||||
elif isinstance(response.data, dict) and ('code' not in response.data and 'result' not in response.data):
|
||||
response.data = {
|
||||
'result': True,
|
||||
'message': 'success',
|
||||
'data': response.data
|
||||
}
|
||||
if response.status_code == status.HTTP_204_NO_CONTENT and request.method == 'DELETE':
|
||||
response.status_code = status.HTTP_200_OK
|
||||
|
||||
return super(ApiGenericMixin, self).finalize_response(request, response, *args, **kwargs)
|
||||
|
||||
return super(ApiGenericMixin, self).finalize_response(
|
||||
request, response, *args, **kwargs
|
||||
)
|
||||
|
||||
class ApiGatewayMixin(object):
|
||||
"""对外开放API返回格式统一
|
||||
|
||||
@@ -84,7 +84,12 @@ class GenericViewSet(ApiGenericMixin, viewsets.GenericViewSet):
|
||||
data.update(request.data)
|
||||
data._mutable = _mutable
|
||||
|
||||
def failure_response(self, msg="failed"):
|
||||
return Response({"result": False, "code": "400", "data": [], "message": msg})
|
||||
|
||||
def success_response(self, msg="success", data=None):
|
||||
data = data or []
|
||||
return Response({"result": True, "code": "200", "data": data, "message": msg})
|
||||
class CreateModelAndLogMixin(mixins.CreateModelMixin):
|
||||
"""
|
||||
Create a model instance and log.
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
猴子补丁实现django中mysql线程池
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from sqlalchemy import event, exc
|
||||
from sqlalchemy.pool import Pool, manage
|
||||
|
||||
|
||||
# POOL_PESSIMISTIC_MODE为True表示每次复用连接池都检查一下连接状态
|
||||
POOL_PESSIMISTIC_MODE = getattr(settings, "DJORM_POOL_PESSIMISTIC", False)
|
||||
|
||||
POOL_SETTINGS = getattr(settings, "DJORM_POOL_OPTIONS", {})
|
||||
POOL_SETTINGS.setdefault("recycle", 3600)
|
||||
POOL_SETTINGS.setdefault("pool_size", 500)
|
||||
print(POOL_SETTINGS)
|
||||
|
||||
@event.listens_for(Pool, "checkout")
|
||||
def _on_checkout(dbapi_connection, connection_record, connection_proxy):
|
||||
|
||||
if POOL_PESSIMISTIC_MODE:
|
||||
cursor = dbapi_connection.cursor()
|
||||
try:
|
||||
cursor.execute("SELECT 1")
|
||||
except Exception:
|
||||
# raise DisconnectionError - pool will try
|
||||
# connecting again up to three times before raising.
|
||||
raise exc.DisconnectionError()
|
||||
finally:
|
||||
cursor.close()
|
||||
|
||||
|
||||
@event.listens_for(Pool, "checkin")
|
||||
def _on_checkin(*args, **kwargs):
|
||||
pass
|
||||
|
||||
@event.listens_for(Pool, "connect")
|
||||
def _on_connect(*args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
def patch_mysql():
|
||||
class HashableDict(dict):
|
||||
def __hash__(self):
|
||||
return hash(frozenset(self))
|
||||
|
||||
class HashableList(list):
|
||||
def __hash__(self):
|
||||
return hash(tuple(sorted(self)))
|
||||
|
||||
class ManagerProxy(object):
|
||||
def __init__(self, manager):
|
||||
self.manager = manager
|
||||
|
||||
def __getattr__(self, key):
|
||||
return getattr(self.manager, key)
|
||||
|
||||
def connect(self, *args, **kwargs):
|
||||
if "conv" in kwargs:
|
||||
conv = kwargs["conv"]
|
||||
if isinstance(conv, dict):
|
||||
items = []
|
||||
for k, v in conv.items():
|
||||
if isinstance(v, list):
|
||||
v = HashableList(v)
|
||||
items.append((k, v))
|
||||
kwargs["conv"] = HashableDict(items)
|
||||
if "ssl" in kwargs:
|
||||
ssl = kwargs["ssl"]
|
||||
if isinstance(ssl, dict):
|
||||
items = []
|
||||
for k, v in ssl.items():
|
||||
if isinstance(v, list):
|
||||
v = HashableList(v)
|
||||
items.append((k, v))
|
||||
kwargs["ssl"] = HashableDict(items)
|
||||
return self.manager.connect(*args, **kwargs)
|
||||
|
||||
try:
|
||||
from django.db.backends.mysql import base as mysql_base
|
||||
except (ImproperlyConfigured, ImportError) as e:
|
||||
return
|
||||
|
||||
if not hasattr(mysql_base, "_Database"):
|
||||
mysql_base._Database = mysql_base.Database
|
||||
mysql_base.Database = ManagerProxy(manage(mysql_base._Database, **POOL_SETTINGS))
|
||||
@@ -1,7 +1,5 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from component.mysql_pool import patch_mysql
|
||||
from .celery_app import app as current_app
|
||||
|
||||
__all__ = ('current_app',)
|
||||
patch_mysql()
|
||||
__all__ = ('current_app',)
|
||||
@@ -7,9 +7,6 @@ import datetime
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(1, os.path.join(os.getcwd(), 'lib'))
|
||||
|
||||
# docker 中redis
|
||||
BROKER_URL = "redis://redis:6379/3"
|
||||
|
||||
SECRET_KEY = 'django-insecure-u5_r=pekio0@zt!y(kgbufuosb9mddu8*qeejkzj@=7uyvb392'
|
||||
|
||||
DEBUG = False
|
||||
@@ -72,18 +69,11 @@ TIME_ZONE = "Asia/Shanghai"
|
||||
LANGUAGE_CODE = "zh-hans"
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.mysql",
|
||||
"NAME": "dj-vue", # noqa bomboo
|
||||
"USER": "root",
|
||||
"PASSWORD": "123456", # xhongc
|
||||
"HOST": "127.0.0.1", # todo docker config mysql
|
||||
"PORT": "3306",
|
||||
# 单元测试 DB 配置,建议不改动
|
||||
"TEST": {"NAME": "test_db", "CHARSET": "utf8", "COLLATION": "utf8_general_ci"},
|
||||
},
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
||||
# Password validation
|
||||
@@ -142,16 +132,7 @@ REST_FRAMEWORK = {
|
||||
"DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S",
|
||||
"NON_FIELD_ERRORS_KEY": "params_error",
|
||||
}
|
||||
DJORM_POOL_PESSIMISTIC = False
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": "redis://127.0.0.1:6379/8",
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JWT_AUTH = {
|
||||
# 过期时间,生成的took七天之后不能使用
|
||||
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
|
||||
@@ -161,6 +142,8 @@ JWT_AUTH = {
|
||||
# 请求头携带的参数
|
||||
'JWT_AUTH_HEADER_PREFIX': 'JWT',
|
||||
}
|
||||
BASE_URL = "https://music.163.com/"
|
||||
|
||||
try:
|
||||
from local_settings import * # noqa
|
||||
except ImportError:
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
FROM nginx
|
||||
|
||||
EXPOSE 80
|
||||
RUN mkdir -p /home/ubuntu/django_vue_log
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
ADD ./docker_file/nginx/nginx-dv.conf /etc/nginx/conf.d/
|
||||
ADD ./templates/index.html /home/ubuntu/
|
||||
@@ -1,14 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name 127.0.0.1;
|
||||
location /static {
|
||||
alias /home/ubuntu/static;
|
||||
}
|
||||
location / {
|
||||
proxy_pass http://django-vue-cli:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
root /home/ubuntu;
|
||||
index index.html;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,18 +1,10 @@
|
||||
celery==4.4.7
|
||||
Django==2.2.6
|
||||
django-celery-beat==2.2.0
|
||||
django-celery-results==1.2.1
|
||||
django-cors-headers==3.2.1
|
||||
django-filter==2.0.0
|
||||
django-mysql==3.8.1
|
||||
djangorestframework==3.8.1
|
||||
mysqlclient==1.4.4
|
||||
python-dateutil==2.8.2
|
||||
redis==3.5.3
|
||||
requests==2.27.1
|
||||
gunicorn==20.1.0
|
||||
gevent==21.12.0
|
||||
sqlalchemy==1.4.42
|
||||
django-redis==5.2.0
|
||||
django-redis-cache==3.0.1
|
||||
djangorestframework-jwt==1.11.0
|
||||
djangorestframework-jwt==1.11.0
|
||||
music-tag==0.4.3
|
||||
Pillow==9.4.0
|
||||
2
static/dist/css/app.css
vendored
2
static/dist/css/app.css
vendored
File diff suppressed because one or more lines are too long
2
static/dist/js/app.js
vendored
2
static/dist/js/app.js
vendored
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<meta name=viewport content="width=device-width,initial-scale=1">
|
||||
<title>Home</title>
|
||||
<title>音乐标签Web版|Music Tag Web|</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Home</title><link href=./static/dist/css/app.css rel=stylesheet></head><body><script>window.siteUrl = "http://127.0.0.1:8080/"
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>音乐标签Web版|Music Tag Web|</title><link href=./static/dist/css/app.css rel=stylesheet></head><body><script>window.siteUrl = "http://127.0.0.1:8080/"
|
||||
window.APP_CODE = 'dj-flow';
|
||||
window.CSRF_COOKIE_NAME = 'django_vue_cli_csrftoken'</script><div id=app></div><script type=text/javascript src=./static/dist/js/manifest.js></script><script type=text/javascript src=./static/dist/js/vendor.js></script><script type=text/javascript src=./static/dist/js/app.js></script></body></html>
|
||||
window.CSRF_COOKIE_NAME = 'django_vue_cli_csrftoken'</script><div id=app></div><script type=text/javascript src=./static/dist/js/manifest.js></script><script type=text/javascript src=./static/dist/js/vendor.js></script><script type=text/javascript src=./static/dist/js/app.js></script></body></html>
|
||||
@@ -9,5 +9,20 @@ export default {
|
||||
},
|
||||
logout: function(params) {
|
||||
return POST(reUrl + '/logout/', params)
|
||||
},
|
||||
fileList: function(params) {
|
||||
return POST(reUrl + '/api/file_list/', params)
|
||||
},
|
||||
musicId3: function(params) {
|
||||
return POST(reUrl + '/api/music_id3/', params)
|
||||
},
|
||||
updateId3: function(params) {
|
||||
return POST(reUrl + '/api/update_id3/', params)
|
||||
},
|
||||
fetchId3Title: function(params) {
|
||||
return POST(reUrl + '/api/fetch_id3_by_title/', params)
|
||||
},
|
||||
fetchLyric: function(params) {
|
||||
return POST(reUrl + '/api/fetch_lyric/', params)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<bk-navigation :default-open="false" navigation-type="left-right" :header-title="headerTitle" :side-title="title"
|
||||
@toggle="handleToggle" class="bk-wrapper">
|
||||
@toggle="handleToggle" :need-menu="false" class="bk-wrapper">
|
||||
<!-- 头部菜单 -->
|
||||
<template slot="header">
|
||||
<top-header></top-header>
|
||||
@@ -9,9 +9,9 @@
|
||||
<img class="monitor-logo-icon" :src="imgPath">
|
||||
</template>
|
||||
<!-- 左侧菜单 -->
|
||||
<template slot="menu">
|
||||
<leftMenu ref="leftMenu"></leftMenu>
|
||||
</template>
|
||||
<!-- <template slot="menu">-->
|
||||
<!-- <leftMenu ref="leftMenu"></leftMenu>-->
|
||||
<!-- </template>-->
|
||||
<!-- 内容区域 -->
|
||||
<container>
|
||||
</container>
|
||||
@@ -22,6 +22,7 @@
|
||||
import topHeader from './header.vue'
|
||||
import leftMenu from './leftMenu.vue'
|
||||
import container from './container.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
topHeader,
|
||||
@@ -72,7 +73,8 @@
|
||||
.bk-wrapper {
|
||||
.bk-navigation-wrapper {
|
||||
.navigation-container {
|
||||
max-width: calc(100% - 60px) !important;
|
||||
max-width: calc(100%) !important;
|
||||
|
||||
.container-content {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ router.beforeEach((to, from, next) => {
|
||||
'name': 'home',
|
||||
'component': 'Home',
|
||||
'meta': {
|
||||
'title': '首页'
|
||||
'title': '音乐标签Web版'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,61 +1,255 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<bk-input :clearable="true" v-model="value"></bk-input>
|
||||
<bk-tree
|
||||
ref="tree1"
|
||||
:data="treeListOne"
|
||||
:node-key="'id'"
|
||||
:has-border="true"
|
||||
@on-click="nodeClickOne"
|
||||
@on-expanded="nodeExpandedOne">
|
||||
</bk-tree>
|
||||
<div style="display: flex;">
|
||||
<div style="width: 350px;margin-top: 20px;margin-left: 10px;">
|
||||
<bk-input :clearable="true" v-model="filePath"
|
||||
@enter="handleSearchFile"
|
||||
:placeholder="'请输入文件夹路径:'"
|
||||
behavior="simplicity">
|
||||
</bk-input>
|
||||
<transition name="bk-slide-fade-down">
|
||||
<div style="margin-top: 10px;" v-show="fadeShowDir">
|
||||
<bk-tree
|
||||
ref="tree1"
|
||||
:data="treeListOne"
|
||||
:node-key="'id'"
|
||||
:has-border="true"
|
||||
@on-click="nodeClickOne"
|
||||
@on-expanded="nodeExpandedOne">
|
||||
</bk-tree>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<transition name="bk-slide-fade-left">
|
||||
<div style="margin-left: 40px;width: 500px;margin-top: 20px;" v-show="musicInfo.title">
|
||||
<div style="width: 100%;">
|
||||
<bk-button :theme="'success'" :loading="isLoading" @click="handleClick" class="mr10"
|
||||
style="width: 100%;">
|
||||
保存信息
|
||||
</bk-button>
|
||||
</div>
|
||||
<div style="display: flex;margin-bottom: 10px;align-items: center;margin-top: 10px;">
|
||||
<div class="label1">标题:</div>
|
||||
<div style="width: 70%;">
|
||||
<bk-input :clearable="true" v-model="musicInfo.title"></bk-input>
|
||||
</div>
|
||||
<div>
|
||||
<bk-icon type="arrows-right-circle" @click="toggleLock('title')"
|
||||
style="cursor: pointer;font-size: 22px;color: #64c864;margin-left: 10px;"></bk-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex;margin-bottom: 10px;align-items: center;">
|
||||
<div class="label1">艺术家:</div>
|
||||
<div style="width: 70%;">
|
||||
<bk-input :clearable="true" v-model="musicInfo.artist"></bk-input>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex;margin-bottom: 10px;align-items: center;">
|
||||
<div class="label1">专辑:</div>
|
||||
<div style="width: 70%;">
|
||||
<bk-input :clearable="true" v-model="musicInfo.album"></bk-input>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex;margin-bottom: 10px;align-items: center;">
|
||||
<div class="label1">风格:</div>
|
||||
<div style="width: 70%;">
|
||||
<bk-select
|
||||
:disabled="false"
|
||||
v-model="musicInfo.genre"
|
||||
style="width: 250px;background: #fff;"
|
||||
ext-cls="select-custom"
|
||||
ext-popover-cls="select-popover-custom"
|
||||
:placeholder="'请选择歌曲风格'"
|
||||
searchable>
|
||||
<bk-option v-for="option in genreList"
|
||||
:key="option.id"
|
||||
:id="option.id"
|
||||
:name="option.name">
|
||||
</bk-option>
|
||||
</bk-select>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex;margin-bottom: 10px;align-items: center;">
|
||||
<div class="label1">年份:</div>
|
||||
<div style="width: 70%;">
|
||||
<bk-input :clearable="true" v-model="musicInfo.year"></bk-input>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex;margin-bottom: 10px;align-items: center;">
|
||||
<div class="label1">歌词:</div>
|
||||
<div style="width: 70%;">
|
||||
<bk-input :clearable="true" v-model="musicInfo.lyrics" type="textarea" :rows="15"
|
||||
></bk-input>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex;margin-bottom: 10px;align-items: center;">
|
||||
<div class="label1">描述:</div>
|
||||
<div style="width: 70%;">
|
||||
<bk-input :clearable="true" v-model="musicInfo.comment" type="textarea"></bk-input>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex;margin-bottom: 10px;align-items: center;">
|
||||
<div class="label1">专辑封面:</div>
|
||||
<div style="width: 70%;">
|
||||
<bk-image fit="contain" :src="musicInfo.album_img" style="width: 128px;"
|
||||
v-show="reloadImg"></bk-image>
|
||||
<bk-image fit="contain" :src="musicInfo.artwork" style="width: 128px;"
|
||||
v-show="!musicInfo.album_img"></bk-image>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<transition name="bk-slide-fade-left">
|
||||
<div
|
||||
style="display: flex;flex-direction: column;margin-top: 20px;flex: 1;margin-right: 20px;margin-left: 20px;"
|
||||
v-show="fadeShowDetail">
|
||||
<div v-if="SongList.length === 0">
|
||||
<span style="margin-left: 30%;margin-top: 30%;">暂无歌曲信息</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="parent">
|
||||
<div class="title2">应用</div>
|
||||
<div class="title2">专辑封面</div>
|
||||
<div class="title2">歌曲名</div>
|
||||
<div class="title2">歌手</div>
|
||||
<div class="title2">专辑</div>
|
||||
<div class="title2">歌词</div>
|
||||
<div class="title2">年份</div>
|
||||
</div>
|
||||
<div v-for="(item,index) in SongList" :key="index" style="margin-bottom: 10px;">
|
||||
<div class="song-card">
|
||||
<div>
|
||||
<div class="parent">
|
||||
<bk-icon type="arrows-left-circle" @click="copyAll(item)"
|
||||
style="font-size: 20px;color: #64c864;margin-right: 5px;cursor: pointer;"></bk-icon>
|
||||
<bk-image fit="contain" :src="item.album_img" style="width: 64px;cursor: pointer;"
|
||||
@click="handleCopy('album_img',item.album_img)">
|
||||
</bk-image>
|
||||
<div @click="handleCopy('title',item.name)" class="music-item">{{ item.name }}</div>
|
||||
<div @click="handleCopy('artist',item.artist)" class="music-item">
|
||||
{{item.artist }}
|
||||
</div>
|
||||
<div @click="handleCopy('album',item.album)" class="music-item">{{ item.album }}</div>
|
||||
<div @click="handleCopy('lyric',item.id)" class="music-item">加载歌词</div>
|
||||
<div @click="handleCopy('year',item.year)" class="music-item">{{ item.year }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
treeListOne: [
|
||||
{
|
||||
name: 'tree node1',
|
||||
title: 'tree node1',
|
||||
expanded: true,
|
||||
id: 1,
|
||||
children: [
|
||||
{
|
||||
name: 'tree node 1-1',
|
||||
title: 'tree node 1-1',
|
||||
expanded: true,
|
||||
children: [
|
||||
{name: 'tree node 1-1-1', title: 'tree node 1-1-1', id: 2},
|
||||
{name: 'tree node 1-1-2', title: 'tree node 1-1-2', id: 3},
|
||||
{name: 'tree node 1-1-3', title: 'tree node 1-1-3', id: 4}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'tree node 1-2',
|
||||
name: 'tree node 1-2',
|
||||
id: 5,
|
||||
expanded: true,
|
||||
children: [
|
||||
{name: 'tree node 1-2-1', title: 'tree node 1-2-1', id: 6},
|
||||
{name: 'tree node 1-2-2', title: 'tree node 1-2-2', id: 7}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
treeListOne: [],
|
||||
filePath: '/Users/macbookair/Music/my_music',
|
||||
fileName: '',
|
||||
musicInfo: {
|
||||
'genre': '流行'
|
||||
},
|
||||
fadeShowDir: false,
|
||||
fadeShowDetail: false,
|
||||
isLoading: false,
|
||||
SongList: [],
|
||||
reloadImg: true,
|
||||
genreList: [
|
||||
{'id': '流行', name: '流行'},
|
||||
{'id': '摇滚', name: '摇滚'},
|
||||
{'id': '说唱', name: '说唱'},
|
||||
{'id': '民谣', name: '民谣'},
|
||||
{'id': '电子', name: '电子'},
|
||||
{'id': '爵士', name: '爵士'},
|
||||
{'id': '纯音乐', name: '纯音乐'},
|
||||
{'id': '金属', name: '金属'},
|
||||
{'id': '世界音乐', name: '世界音乐'},
|
||||
{'id': '新世纪', name: '新世纪'},
|
||||
{'id': '古典', name: '古典'},
|
||||
{'id': '独立', name: '独立'},
|
||||
{'id': '氛围音乐', name: '氛围音乐'}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
nodeClickOne(node) {
|
||||
console.log(node)
|
||||
this.musicInfo = {}
|
||||
this.fileName = node.name
|
||||
this.$api.Task.musicId3({'file_path': this.filePath, 'file_name': node.name}).then((res) => {
|
||||
console.log(res)
|
||||
this.musicInfo = res.data
|
||||
})
|
||||
},
|
||||
handleCopy(k, v) {
|
||||
if (k === 'lyric') {
|
||||
this.$api.Task.fetchLyric({'song_id': v}).then((res) => {
|
||||
console.log(res)
|
||||
if (res.result) {
|
||||
this.musicInfo['lyrics'] = res.data
|
||||
} else {
|
||||
this.$cwMessage('未找到歌词', 'error')
|
||||
}
|
||||
})
|
||||
} else if (k === 'album_img') {
|
||||
this.musicInfo[k] = v
|
||||
this.reloadImg = false
|
||||
this.$nextTick(() => {
|
||||
this.reloadImg = true
|
||||
})
|
||||
} else {
|
||||
this.musicInfo[k] = v
|
||||
}
|
||||
},
|
||||
copyAll(item) {
|
||||
this.handleCopy('title', item.name)
|
||||
this.handleCopy('year', item.year)
|
||||
this.handleCopy('lyric', item.id)
|
||||
this.handleCopy('album', item.album)
|
||||
this.handleCopy('artist', item.artist)
|
||||
this.handleCopy('album_img', item.album_img)
|
||||
},
|
||||
nodeExpandedOne(node, expanded) {
|
||||
console.log(node)
|
||||
console.log(expanded)
|
||||
},
|
||||
// 查询网易云接口
|
||||
toggleLock(mode) {
|
||||
if (mode === 'title') {
|
||||
if (!this.musicInfo.title) {
|
||||
this.$cwMessage('标题不能为空', 'error')
|
||||
return
|
||||
}
|
||||
this.fadeShowDetail = false
|
||||
this.$api.Task.fetchId3Title({title: this.musicInfo.title}).then((res) => {
|
||||
this.fadeShowDetail = true
|
||||
this.SongList = res.data
|
||||
})
|
||||
}
|
||||
},
|
||||
// 文件目录
|
||||
handleSearchFile() {
|
||||
this.fadeShowDir = false
|
||||
this.$api.Task.fileList({'file_path': this.filePath}).then((res) => {
|
||||
if (res.result) {
|
||||
this.treeListOne = res.data
|
||||
this.fadeShowDir = true
|
||||
}
|
||||
})
|
||||
},
|
||||
// 保存音乐信息
|
||||
handleClick() {
|
||||
const params = [{
|
||||
'file_full_path': this.filePath + '/' + this.fileName,
|
||||
...this.musicInfo
|
||||
}]
|
||||
this.isLoading = true
|
||||
this.$api.Task.updateId3({'music_id3_info': params}).then((res) => {
|
||||
this.isLoading = false
|
||||
if (res.result) {
|
||||
this.$cwMessage('修改成功', 'success')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,4 +261,35 @@
|
||||
text-decoration-style: dashed;
|
||||
text-underline-position: under;
|
||||
}
|
||||
|
||||
.music-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.music-item:hover {
|
||||
color: #1facdd;
|
||||
}
|
||||
|
||||
.label1 {
|
||||
width: 80px;
|
||||
}
|
||||
.parent {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
grid-template-rows: repeat(1, 1fr);
|
||||
grid-column-gap: 0;
|
||||
grid-row-gap: 0;
|
||||
place-items: center;
|
||||
}
|
||||
.title2 {
|
||||
font-weight: 500;
|
||||
}
|
||||
.song-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #E2E2E2;
|
||||
}
|
||||
.song-card:hover {
|
||||
background: #E2E2E2;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user