Skip to content

Commit

Permalink
✨ 添加Spotify Api
Browse files Browse the repository at this point in the history
  • Loading branch information
Mai-icy committed Oct 21, 2022
1 parent c406493 commit 184f8ff
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 9 deletions.
1 change: 1 addition & 0 deletions MusicTager/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
# -*- coding:utf-8 -*-
from .kugou_api import KugouApi
from .cloud_api import CloudMusicWebApi
from .spotify_api import SpotifyApi
from .api_error import NoneResultError
2 changes: 1 addition & 1 deletion MusicTager/api/cloud_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def get_song_info(self, song_id: str) -> SongInfo:
duration = song_json["duration"] // 1000

pic_url = song_json["album"]["picUrl"]

print(pic_url)
pic_data = requests.get(pic_url, timeout=4).content
pic_buffer = io.BytesIO(pic_data)

Expand Down
106 changes: 106 additions & 0 deletions MusicTager/api/spotify_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/python
# -*- coding:utf-8 -*-
import re
import io
import time

import base64
from typing import List, Dict
import requests
from lyric_decode.lyric_decode import KrcFile
from song_metadata.metadata_type import SongInfo, SongSearchInfo
from api.api_error import NoneResultError

from PIL import Image


CLIENT_ID = '2fa0e4d172014c33809ff17e526d8559'
CLIENT_SECRET = '0e7d3cfe97794639966683001ca45eec'


class SpotifyAuth:
AUTH_AUTHORIZE_URL = "https://accounts.spotify.com/authorize"
AUTH_TOKEN_URL = "https://accounts.spotify.com/api/token"

def __init__(self, client_id, client_secret):
self.token_info = None
self.client_id = client_id
self.client_secret = client_secret

auth = base64.b64encode((CLIENT_ID + ":" + CLIENT_SECRET).encode("ascii"))
self.auth_header = {'Authorization': 'Basic ' + auth.decode("ascii")}
self._fetch_access_token()

def get_token(self):
if self.token_info["expires_at"] > int(time.time()):
return self.token_info["access_token"]
else:
self._fetch_access_token()
return self.token_info["access_token"]

def _fetch_access_token(self):
"""获取token"""
payload = {"grant_type": "client_credentials"}
response = requests.post(self.AUTH_TOKEN_URL, headers=self.auth_header, data=payload)
response.raise_for_status()
self.token_info = response.json()
self.token_info["expires_at"] = int(time.time()) + self.token_info["expires_in"]


class SpotifyApi:
SEARCH_URL = "https://api.spotify.com/v1/search"

def __init__(self):
self.auth = SpotifyAuth(CLIENT_ID, CLIENT_SECRET)
self.header = {"Authorization": "Bearer {0}".format(self.auth.get_token()),
"Content-Type": "application/json"}
self.session = requests.session()

def search_data(self, keyword: str, offset: int = 0, limit=10):
keyword = re.sub(r"|[!@#$%^&*/]+", "", keyword)
params = {
"query": keyword,
"type": "track",
"offset": offset,
"limit": limit
}
res_json = self.session.get(self.SEARCH_URL, headers=self.header, params=params).json()
song_info_list = []

for data in res_json['tracks']['items']:
artists_list = [info["name"] for info in data["artists"]]
duration = data["duration_ms"] // 1000
song_info = {
"singer": ','.join(artists_list),
"songName": data["name"],
"duration": f'{duration // 60}:{duration % 60 // 10}{duration % 10}',
"idOrMd5": data["id"]
}
song_info_list.append(SongSearchInfo(**song_info))
return song_info_list

def get_song_info(self, song_id: str) -> SongInfo:
url = "https://api.spotify.com/v1/tracks/{}".format(song_id)

song_json = requests.get(url, headers=self.header).json()
artists_list = [info["name"] for info in song_json["artists"]]
duration = int(song_json["duration_ms"]) // 1000
pic_url = song_json["album"]["images"][-1]["url"]
pic_data = requests.get(pic_url, timeout=4).content
pic_buffer = io.BytesIO(pic_data)
song_info = {
"singer": ','.join(artists_list),
"songName": song_json["name"],
"album": song_json["album"]["name"],
"year": song_json["album"]["release_date"][:4],
"trackNumber": (song_json["track_number"], None),
"duration": f'{duration // 60}:{duration % 60 // 10}{duration % 10}',
"genre": None,
"picBuffer": pic_buffer}
return SongInfo(**song_info)


if __name__ == "__main__":
t1 = SpotifyApi()
t1.search_data("miku")
print(t1.get_song_info("5mJ8Sj9EqT9UgpWY1RnEi2"))
12 changes: 12 additions & 0 deletions MusicTager/components/dialog/setting_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
class ApiMode(Enum):
CLOUD = 0
KUGOU = 1
SPOTIFY = 2


class SettingDialog(QDialog, Ui_SettingDialog):
Expand All @@ -31,6 +32,17 @@ def __init__(self, parent=None):
def _init_signal(self):
self.auto_button.clicked.connect(self.auto_event)

self.api_comboBox.currentIndexChanged.connect(self.comboBox_event)

def comboBox_event(self):
"""spotify暂时不支持下载歌词"""
if self.api_comboBox.currentIndex() == 2: # 选中到spotify api,禁用打开自动下载歌词
self.is_download_lrc_checkBox.setChecked(False)
self.is_download_lrc_checkBox.setEnabled(False)
else:
if not self.is_download_lrc_checkBox.isEnabled():
self.is_download_lrc_checkBox.setEnabled(True)

def auto_event(self):
self.auto_if = True
self.accept()
Expand Down
22 changes: 22 additions & 0 deletions MusicTager/components/info_label.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/python
# -*- coding:utf-8 -*-

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *


class InfoLabel(QLabel):
def __init__(self, parent=None):
super(InfoLabel, self).__init__(parent=parent)
self.setTextInteractionFlags(Qt.TextSelectableByMouse) # 设置label可复制
self.metrics = QFontMetrics(self.font())

def put_text(self, text: str):
text = text if text else "N/A"
new_text = self.metrics.elidedText(text, Qt.ElideRight, self.width())
self.setText(new_text)




41 changes: 33 additions & 8 deletions MusicTager/ui/metadata_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(self, parent=None):
self.auto_dialog = dialog.AutoMetadataDialog(self)
self.cloud_api = api.CloudMusicWebApi()
self.kugou_api = api.KugouApi()
self.spotify_api = api.SpotifyApi()

self._init_signal()
self._init_table_widgets()
Expand Down Expand Up @@ -251,6 +252,9 @@ def _thread_auto_complete(self):
elif self.api_mode == ApiMode.KUGOU:
search_func = self.kugou_api.search_hash
search_info_func = self.kugou_api.get_song_info
elif self.api_mode == ApiMode.SPOTIFY:
search_func = self.spotify_api.search_data
search_info_func = self.spotify_api.get_song_info
else:
raise ValueError("api_mode参数错误,未知的模式")

Expand All @@ -275,6 +279,10 @@ def _thread_auto_complete(self):
score = sm.compare_song_info(song_info, res_info)
if score >= 80:
self.write_metadata(row, res_info, search_data[0].idOrMd5)
elif len(search_data) > 1:
res_info = search_info_func(search_data[1].idOrMd5)
if score >= 80:
self.write_metadata(row, res_info, search_data[1].idOrMd5)
self.auto_dialog.add_signal.emit("")
else:
self.auto_dialog.add_signal.emit("")
Expand Down Expand Up @@ -305,6 +313,8 @@ def _load_search_data(self) -> None:

def _load_song_info(self) -> None:
"""加载搜索结果的数据到ui"""
if not self.song_info:
return
self.set_left_text(self.result_genre_label, 'N/A')
self.set_left_text(self.result_year_label, self.song_info.year)
self.set_left_text(self.result_album_label, self.song_info.album)
Expand All @@ -315,6 +325,7 @@ def _load_song_info(self) -> None:
self.set_left_text(self.result_track_number_label, str(self.song_info.trackNumber[0]))
else:
self.set_left_text(self.result_track_number_label, "N/A")

if self.song_info.picBuffer.getvalue():
pix = Image.open(self.song_info.picBuffer).toqpixmap()
self.result_pic_label.setPixmap(pix)
Expand All @@ -329,6 +340,8 @@ def download_lrc(self, md5_or_id: str, save_name: str) -> None:
elif self.api_mode == ApiMode.KUGOU:
lrc_info = self.kugou_api.get_lrc_info(md5_or_id)[0]
lrc_file = self.kugou_api.get_lrc(lrc_info)
elif self.api_mode == ApiMode.SPOTIFY:
return
else:
raise ValueError("api_mode参数错误,未知的模式")
if not os.path.exists(LRC_PATH):
Expand All @@ -347,6 +360,8 @@ def search_event(self, *args) -> None:
self.search_data = self.cloud_api.search_data(keyword)
elif self.api_mode == ApiMode.KUGOU:
self.search_data = self.kugou_api.search_hash(keyword)
elif self.api_mode == ApiMode.SPOTIFY:
self.search_data = self.spotify_api.search_data(keyword)
else:
raise ValueError("api_mode参数错误,未知的模式")
except api.NoneResultError:
Expand All @@ -362,14 +377,24 @@ def result_click_event(self, item) -> None:
self.search_tableWidget.setEnabled(False)
self.result_pic_label.clear()
self.result_pic_label.setText("获取数据中")
if self.api_mode == ApiMode.CLOUD:
song_id = self.search_tableWidget.item(item.row(), 3).text()
self.song_info = self.cloud_api.get_song_info(song_id)
elif self.api_mode == ApiMode.KUGOU:
md5 = self.search_tableWidget.item(item.row(), 3).text()
self.song_info = self.kugou_api.get_song_info(md5)
else:
raise ValueError("api_mode参数错误,未知的模式")
try:
if self.api_mode == ApiMode.CLOUD:
song_id = self.search_tableWidget.item(item.row(), 3).text()
self.song_info = self.cloud_api.get_song_info(song_id)
elif self.api_mode == ApiMode.KUGOU:
md5 = self.search_tableWidget.item(item.row(), 3).text()
self.song_info = self.kugou_api.get_song_info(md5)
elif self.api_mode == ApiMode.SPOTIFY:
song_id = self.search_tableWidget.item(item.row(), 3).text()
self.song_info = self.spotify_api.get_song_info(song_id)
else:
raise ValueError("api_mode参数错误,未知的模式")
except api.NoneResultError:
self.search_data = []
except Exception as e:
self.warning_dialog_show_signal.emit(repr(e))
self.search_data = []
return
self.search_tableWidget.setEnabled(True)
self.file_listWidget.setEnabled(True)

Expand Down
2 changes: 2 additions & 0 deletions MusicTager/ui/ui_source/SettingDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def setupUi(self, MetadataDialogSetting):
self.api_comboBox.setObjectName("api_comboBox")
self.api_comboBox.addItem("")
self.api_comboBox.addItem("")
self.api_comboBox.addItem("")
self.horizontalLayout_3.addWidget(self.api_comboBox)
self.verticalLayout.addLayout(self.horizontalLayout_3)
self.is_download_lrc_checkBox = QtWidgets.QCheckBox(MetadataDialogSetting)
Expand Down Expand Up @@ -72,6 +73,7 @@ def retranslateUi(self, MetadataDialogSetting):
self.label.setText(_translate("MetadataDialogSetting", "元数据来源:"))
self.api_comboBox.setItemText(0, _translate("MetadataDialogSetting", "网易云api"))
self.api_comboBox.setItemText(1, _translate("MetadataDialogSetting", "酷狗api"))
self.api_comboBox.setItemText(2, _translate("MetadataDialogSetting", "spotify api"))
self.is_download_lrc_checkBox.setText(_translate("MetadataDialogSetting", "补全同时下载歌词"))
self.is_rename_file_checkBox.setText(_translate("MetadataDialogSetting", "修改元数据同时修改文件名"))
self.auto_button.setText(_translate("MetadataDialogSetting", "自动补全元数据"))
Expand Down

0 comments on commit 184f8ff

Please sign in to comment.