desktop lyric. downloading songs. fixed some bugs.
This commit is contained in:
@@ -82,4 +82,13 @@ QHeaderView::section {
|
||||
|
||||
QHeaderView::section:hover {
|
||||
background-color: #232529;
|
||||
}
|
||||
|
||||
QMenu {
|
||||
background: #302F33;
|
||||
color: #D1D1D7;
|
||||
}
|
||||
|
||||
QMenu::item:selected {
|
||||
background: #3B3A3D;
|
||||
}
|
||||
@@ -1,60 +1,3 @@
|
||||
/*QWidget#MainWindow{
|
||||
background: #222225;
|
||||
}
|
||||
|
||||
QFrame#line1{
|
||||
color: qlineargradient(spread:reflect, x1:0.5, y1:0, x2:1, y2:0, stop:0 rgba(102, 4, 4, 255), stop:0.397727 rgba(184, 37, 37, 255));
|
||||
|
||||
}
|
||||
|
||||
QFrame {
|
||||
border: none;
|
||||
}
|
||||
|
||||
QScrollBar:vertical{
|
||||
background: #191B1F;
|
||||
width: 7px;
|
||||
margin: 0px 0 0px 0;
|
||||
}
|
||||
|
||||
QScrollBar::handle:vertical{
|
||||
background: #2F3134;
|
||||
min-height: 20px;
|
||||
margin: 0 0 0 0;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
QScrollBar::handle:hover{
|
||||
background: #3B3C40;
|
||||
min-height: 20px;
|
||||
margin: 0 0 0 0;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
QScrollBar::add-line:vertical{
|
||||
background: #191B1F;
|
||||
height: 0px;
|
||||
subcontrol-position: bottom;
|
||||
subcontrol-origin: margin;
|
||||
}
|
||||
|
||||
QScrollBar::sub-line:vertical{
|
||||
background: #191B1F;
|
||||
height: 0px;
|
||||
subcontrol-position: top;
|
||||
subcontrol-origin: margin;
|
||||
}
|
||||
|
||||
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical{
|
||||
background: none;
|
||||
}
|
||||
|
||||
QScrollBar:horizontal {
|
||||
height: 0px;
|
||||
}*/
|
||||
|
||||
QWidget#MainWindow{
|
||||
background: #222225;
|
||||
}
|
||||
@@ -119,4 +62,4 @@ QTabWidget {
|
||||
QTabBar#mainTab::tab{
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
__author__ = 'cyrbuzz'
|
||||
|
||||
import re
|
||||
import os
|
||||
|
||||
import asyncio
|
||||
import pickle
|
||||
|
||||
from apiRequestsBase import HttpRequest
|
||||
from asyncBase import aAsync, toTask
|
||||
from base import QAction, checkFolder, QIcon, QLabel, QObject, RequestThread, QTableWidgetItem
|
||||
from base import QAction, QMenu, checkFolder, QIcon, QLabel, QObject, RequestThread, QTableWidgetItem, QCursor
|
||||
from singsFrameBase import PlaylistButton
|
||||
from netEaseApi import netease
|
||||
from xiamiApi import xiami
|
||||
from qqApi import qqApi
|
||||
# import xiamiApi
|
||||
|
||||
import addition
|
||||
|
||||
# netEase = netEaseApi.NetEaseWebApi()
|
||||
# xiami = xiamiApi.XiamiApi()
|
||||
|
||||
myRequests = HttpRequest()
|
||||
|
||||
|
||||
class ConfigWindow(QObject):
|
||||
@@ -363,25 +367,81 @@ class ConfigSearchArea(QObject):
|
||||
def __init__(self, searchArea):
|
||||
super(ConfigSearchArea, self).__init__()
|
||||
|
||||
# current show-table's index.
|
||||
self.currentIndex = 0
|
||||
# current widgets name。
|
||||
self.currentName = '网易云'
|
||||
|
||||
# parent.
|
||||
self.searchArea = searchArea
|
||||
|
||||
self.transTime = addition.itv2time
|
||||
|
||||
self.searchEngineers = {'网易云': netease, '虾米': xiami, 'QQ': qqApi}
|
||||
# TODO
|
||||
# to config singsFrameBase instead of configing them respective.
|
||||
self.searchResultTableIndexs = {'网易云':self.searchArea.neteaseSearchFrame.singsResultTable,
|
||||
'虾米':self.searchArea.xiamiSearchFrame.singsResultTable ,
|
||||
'QQ':self.searchArea.qqSearchFrame.singsResultTable}
|
||||
|
||||
self.musicList = []
|
||||
self.noContents = "很抱歉 未能找到关于<font style='text-align: center;' color='#23518F'>“{0}”</font>的{1}。"
|
||||
|
||||
self.bindConnect()
|
||||
self.setContextMenu()
|
||||
|
||||
def bindConnect(self):
|
||||
self.searchArea.contentsTab.tabBarClicked.connect(self.searchBy)
|
||||
self.searchArea.neteaseSearchFrame.singsResultTable.itemDoubleClicked.connect(self.itemDoubleClickedEvent)
|
||||
self.searchArea.xiamiSearchFrame.singsResultTable.itemDoubleClicked.connect(self.itemDoubleClickedEvent)
|
||||
self.searchArea.qqSearchFrame.singsResultTable.itemDoubleClicked.connect(self.itemDoubleClickedEvent)
|
||||
|
||||
|
||||
self.searchArea.neteaseSearchFrame.singsResultTable.contextMenuEvent = self.contextEvent
|
||||
self.searchArea.xiamiSearchFrame.singsResultTable.contextMenuEvent = self.contextEvent
|
||||
self.searchArea.qqSearchFrame.singsResultTable.contextMenuEvent = self.contextEvent
|
||||
|
||||
def setContextMenu(self):
|
||||
self.actionDownloadSong = QAction('下载', self)
|
||||
self.actionDownloadSong.triggered.connect(self.downloadSong)
|
||||
|
||||
@toTask
|
||||
def downloadSong(self, x):
|
||||
# x is not to use, but must be.
|
||||
musicInfo = self.musicList[self.currentIndex]
|
||||
url = musicInfo.get('url')
|
||||
if 'http:' not in url and 'https:' not in url:
|
||||
songId = musicInfo.get('music_id')
|
||||
future = aAsync(netease.singsUrl, [songId])
|
||||
url = yield from future
|
||||
url = url[0].get('url')
|
||||
musicInfo['url'] = url
|
||||
else:
|
||||
url = musicInfo.get('url')
|
||||
|
||||
|
||||
future = aAsync(myRequests.httpRequest, url, 'GET')
|
||||
data = yield from future
|
||||
|
||||
if 'downloads' not in os.listdir('.'):
|
||||
os.mkdir('downloads')
|
||||
|
||||
allMusicName = re.search(r'.*\.[a-zA-Z0-9]+', url[url.rfind('/')+1:]).group(0)
|
||||
if allMusicName:
|
||||
|
||||
musicSuffix = allMusicName[allMusicName.rfind('.')+1:]
|
||||
musicName = '{name}.{suf}'.format(name=musicInfo.get('name') + ' - ' + musicInfo.get('author'), suf=musicSuffix)
|
||||
else:
|
||||
# TODO MD5。
|
||||
musicName = "random_name.mp3"
|
||||
|
||||
with open('downloads/{musicName}'.format(musicName=musicName), 'wb') as f:
|
||||
f.write(data.content)
|
||||
|
||||
self.searchArea.parent.systemTray.showMessage("~~~", '{musicName} 下载完成'.format(musicName=musicName))
|
||||
|
||||
def searchBy(self, index):
|
||||
currentWidgetName = self.searchArea.contentsTab.tabText(index)
|
||||
self.currentName = currentWidgetName
|
||||
self.search(currentWidgetName)
|
||||
|
||||
@toTask
|
||||
@@ -460,6 +520,24 @@ class ConfigSearchArea(QObject):
|
||||
data = self.musicList[currentRow]
|
||||
self.searchArea.parent.playWidgets.setPlayerAndPlayList(data)
|
||||
|
||||
def contextEvent(self, event):
|
||||
currentWidget = self.searchResultTableIndexs.get(self.currentName)
|
||||
if not currentWidget:
|
||||
return
|
||||
|
||||
item = currentWidget.itemAt(currentWidget.mapFromGlobal(QCursor.pos()))
|
||||
self.menu = QMenu(currentWidget)
|
||||
|
||||
self.menu.addAction(self.actionDownloadSong)
|
||||
|
||||
try:
|
||||
self.currentIndex = item.row() - 1
|
||||
# 在索引是最后一行时会获取不到。
|
||||
except:
|
||||
self.currentIndex = -1
|
||||
|
||||
self.menu.exec_(QCursor.pos())
|
||||
|
||||
|
||||
class ConfigSystemTray(QObject):
|
||||
|
||||
|
||||
@@ -63,24 +63,31 @@ class ConfigNative(QObject):
|
||||
self.native.singsTable.setRowCount(self.native.singsTable.rowCount()+length)
|
||||
self.musicList = []
|
||||
for i in enumerate(mediaFiles):
|
||||
|
||||
music = eyed3.load(i[1])
|
||||
|
||||
if not music:
|
||||
self.singsTable.removeRow(i[0])
|
||||
continue
|
||||
|
||||
try:
|
||||
name = music.tag.title
|
||||
except:
|
||||
print('获取该歌曲信息出错: {},已跳过。'.format(i))
|
||||
self.singsTable.removeRow(i[0])
|
||||
continue
|
||||
author = music.tag.artist
|
||||
|
||||
if not name:
|
||||
name = i[1].split('\\')[-1][:-4]
|
||||
author = music.tag.artist
|
||||
if not author:
|
||||
author = '未知歌手'
|
||||
if not name:
|
||||
filePath = i[1].replace(selectFolder, '')
|
||||
name = filePath[1:][:-4]
|
||||
if not author:
|
||||
author = ''
|
||||
except:
|
||||
try:
|
||||
# TODO
|
||||
# if more folders exist.
|
||||
filePath = i[1].replace(selectFolder, '')
|
||||
name = filePath[1:][:-4]
|
||||
except Exception as e:
|
||||
name = i[1]
|
||||
author = ''
|
||||
|
||||
time = itv2time(music.info.time_secs)
|
||||
|
||||
self.musicList.append({'name': name, 'author': author, 'time': time, 'url': i[1], 'music_img': 'None'})
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"""
|
||||
__author__ = 'cyrbuzz'
|
||||
import os
|
||||
import re
|
||||
|
||||
import network
|
||||
import addition
|
||||
@@ -12,6 +13,15 @@ from base import (QAction, QCursor, QFrame, QLabel, QObject, QPixmap, QRunnable,
|
||||
from netEaseApi import netease
|
||||
from singsFrameBase import OneSing, PlaylistButton
|
||||
|
||||
# ../features
|
||||
from asyncBase import aAsync, toTask
|
||||
|
||||
# ../apis
|
||||
from netEaseApi import netease
|
||||
from apiRequestsBase import HttpRequest
|
||||
|
||||
myRequests = HttpRequest()
|
||||
|
||||
|
||||
transTime = addition.itv2time
|
||||
|
||||
@@ -231,12 +241,48 @@ class ConfigDetailSings(QObject):
|
||||
self.actionNextPlay = QAction('下一首播放', self)
|
||||
self.actionNextPlay.triggered.connect(self.addToNextPlay)
|
||||
|
||||
self.actionDownloadSong = QAction('下载', self)
|
||||
self.actionDownloadSong.triggered.connect(self.downloadSong)
|
||||
|
||||
def addToNextPlay(self):
|
||||
data = self.musicList[self.currentIndex]
|
||||
self.player.setAllMusics([data])
|
||||
self.playList.playList.addMusic(data)
|
||||
self.playList.playList.addPlayList(data['name'], data['author'], data['time'])
|
||||
|
||||
@toTask
|
||||
def downloadSong(self, x):
|
||||
musicInfo = self.musicList[self.currentIndex]
|
||||
url = musicInfo.get('url')
|
||||
if 'http:' not in url and 'https:' not in url:
|
||||
songId = musicInfo.get('music_id')
|
||||
future = aAsync(netease.singsUrl, [songId])
|
||||
url = yield from future
|
||||
url = url[0].get('url')
|
||||
musicInfo['url'] = url
|
||||
else:
|
||||
url = musicInfo.get('url')
|
||||
|
||||
future = aAsync(myRequests.httpRequest, url, 'GET')
|
||||
data = yield from future
|
||||
|
||||
if 'downloads' not in os.listdir('.'):
|
||||
os.mkdir('downloads')
|
||||
|
||||
allMusicName = re.search(r'.*\.[a-zA-Z0-9]+', url[url.rfind('/')+1:]).group(0)
|
||||
if allMusicName:
|
||||
|
||||
musicSuffix = allMusicName[allMusicName.rfind('.')+1:]
|
||||
musicName = '{name}.{suf}'.format(name=musicInfo.get('name') + ' - ' + musicInfo.get('author'), suf=musicSuffix)
|
||||
else:
|
||||
# TODO MD5。
|
||||
musicName = "random_name.mp3"
|
||||
|
||||
with open('downloads/{musicName}'.format(musicName=musicName), 'wb') as f:
|
||||
f.write(data.content)
|
||||
|
||||
self.grandparent.systemTray.showMessage("~~~", '{musicName} 下载完成'.format(musicName=musicName))
|
||||
|
||||
def addAllMusicToPlayer(self):
|
||||
self.playList.setPlayerAndPlaylists(self.musicList)
|
||||
|
||||
@@ -295,6 +341,7 @@ class ConfigDetailSings(QObject):
|
||||
self.menu = QMenu(self.detailSings.singsTable)
|
||||
|
||||
self.menu.addAction(self.actionNextPlay)
|
||||
self.menu.addAction(self.actionDownloadSong)
|
||||
|
||||
try:
|
||||
self.currentIndex = item.row() - 1
|
||||
|
||||
+24
-13
@@ -35,7 +35,7 @@ import logging
|
||||
from quamash import QEventLoop
|
||||
|
||||
# widgets
|
||||
from base import (QApplication, QDialog, QFrame, QHBoxLayout, HBoxLayout, QIcon, QLabel, QListWidget, QListWidgetItem,
|
||||
from base import (QApplication, cacheFolder, QDialog, QFrame, QHBoxLayout, HBoxLayout, QIcon, QLabel, QListWidget, QListWidgetItem,
|
||||
QPushButton, PicLabel, QScrollArea, ScrollArea, Qt, QTabWidget, TableWidget, QVBoxLayout, VBoxLayout,
|
||||
QWidget)
|
||||
from player import PlayWidgets
|
||||
@@ -65,6 +65,8 @@ logger.loggerConfig('logger/running_log.log')
|
||||
# 覆盖原logger变量。
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
logger.info("当前图片缓存目录: {0}".format(os.path.join(os.getcwd(), cacheFolder)))
|
||||
|
||||
|
||||
# 用于承载整个界面。所有窗口的父窗口,所有窗口都可以在父窗口里找到索引。
|
||||
class Window(QWidget):
|
||||
@@ -178,6 +180,12 @@ class Window(QWidget):
|
||||
self.indexNetEaseSings.config.initThread()
|
||||
self.indexXiamiSings.config.initThread()
|
||||
self.indexQQSings.config.initThread()
|
||||
|
||||
# test desktop lyric。
|
||||
screen = QApplication.desktop().availableGeometry()
|
||||
self.playWidgets.desktopLyric.resize(screen.width(), 50)
|
||||
self.playWidgets.desktopLyric.move(0, screen.height() - 100)
|
||||
self.playWidgets.desktopLyric.show()
|
||||
|
||||
def closeEvent(self, event):
|
||||
# 主要是保存cookies.
|
||||
@@ -252,7 +260,7 @@ class Header(QFrame):
|
||||
self.logoLabel = PicLabel(r'resource/format.png', 32, 32)
|
||||
|
||||
self.descriptionLabel = QLabel(self)
|
||||
self.descriptionLabel.setText("<b>Music<b>")
|
||||
self.descriptionLabel.setText("<b>Music</b>")
|
||||
|
||||
self.userPix = PicLabel(r'resource/no_music.png', 32, 32, r'resource/user_pic_mask.png')
|
||||
self.userPix.setMinimumSize(22, 22)
|
||||
@@ -489,20 +497,23 @@ def start():
|
||||
# 是被asyncio重写的事件循环。
|
||||
eventLoop = QEventLoop(app)
|
||||
asyncio.set_event_loop(eventLoop)
|
||||
main = Window()
|
||||
|
||||
main.show()
|
||||
# 当前音乐的显示信息。
|
||||
# 因为需要布局之后重新绘制的宽高。
|
||||
# 这个宽高会在show之后才会改变。
|
||||
# 需要获取宽,高并嵌入到父窗口里。
|
||||
main.playWidgets.currentMusic.resize(main.navigation.width(), 64)
|
||||
|
||||
with eventLoop:
|
||||
eventLoop.run_forever()
|
||||
try:
|
||||
main = Window()
|
||||
|
||||
sys.exit(0)
|
||||
main.show()
|
||||
# 当前音乐的显示信息。
|
||||
# 因为需要布局之后重新绘制的宽高。
|
||||
# 这个宽高会在show之后才会改变。
|
||||
# 需要获取宽,高并嵌入到父窗口里。
|
||||
main.playWidgets.currentMusic.resize(main.navigation.width(), 64)
|
||||
|
||||
with eventLoop:
|
||||
eventLoop.run_forever()
|
||||
|
||||
sys.exit(0)
|
||||
except:
|
||||
logger.error("got some error", exc_info=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
start()
|
||||
|
||||
@@ -287,7 +287,6 @@ picsQueue = QueueObject()
|
||||
# 缓存目录。
|
||||
cacheFolder = 'cache'
|
||||
|
||||
logger.info("当前缓存目录: {0}".format(os.getcwd()+cacheFolder))
|
||||
|
||||
## 对<img src=1.jpg>的初步探索。
|
||||
# 暂只接受http(s)和本地目录。
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file copy from https://github.com/wn0112/PPlayer
|
||||
|
||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||
|
||||
def _fromUtf8(s):
|
||||
return s
|
||||
|
||||
class Button(QtWidgets.QPushButton):
|
||||
def __init__(self,parent = None):
|
||||
super(Button,self).__init__(parent)
|
||||
self.status = 0
|
||||
|
||||
def loadPixmap(self, pic_name):
|
||||
self.pixmap = QtGui.QPixmap(pic_name)
|
||||
self.btn_width = self.pixmap.width()
|
||||
self.btn_height = self.pixmap.height()/6
|
||||
self.setFixedSize(self.btn_width, self.btn_height)
|
||||
|
||||
def mousePressEvent(self,event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
self.status = 2
|
||||
self.update()
|
||||
self.clicked.emit(True)
|
||||
|
||||
def mouseReleaseEvent(self,event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
self.status = 0
|
||||
self.update()
|
||||
self.released.emit()
|
||||
|
||||
def rst(self):
|
||||
self.status = 0
|
||||
self.update()
|
||||
|
||||
def rbReleased(self):
|
||||
self.status = 0
|
||||
self.update()
|
||||
|
||||
def rbPressed(self):
|
||||
self.status = 4
|
||||
self.update()
|
||||
|
||||
def lbPressed(self):
|
||||
self.status = 3
|
||||
self.update()
|
||||
|
||||
def lbReleased(self):
|
||||
self.status = 0
|
||||
self.update()
|
||||
|
||||
def bothPressed(self):
|
||||
self.status = 5
|
||||
self.update()
|
||||
|
||||
def bothReleased(self):
|
||||
self.status = 0
|
||||
self.update()
|
||||
|
||||
def paintEvent(self,event):
|
||||
self.painter = QtGui.QPainter()
|
||||
self.painter.begin(self)
|
||||
self.painter.drawPixmap(self.rect(), self.pixmap.copy(0, self.btn_height * self.status, self.btn_width, self.btn_height))
|
||||
self.painter.end()
|
||||
|
||||
class PlayButton(Button):
|
||||
def __init__(self,parent = None):
|
||||
super(PlayButton,self).__init__(parent)
|
||||
self.setCheckable(True)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
self.released.emit()
|
||||
|
||||
class SRButton(PlayButton):
|
||||
def __init__(self,parent = None):
|
||||
super(SRButton,self).__init__(parent)
|
||||
self.syn = 0
|
||||
|
||||
def mousePressEvent(self,event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
self.status = 2
|
||||
self.update()
|
||||
if not self.isChecked():
|
||||
self.syn = 1
|
||||
self.clicked.emit(True)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self.isChecked() and self.syn != 1:
|
||||
self.clicked.emit(True)
|
||||
self.released.emit()
|
||||
else:
|
||||
self.syn = 0
|
||||
|
||||
|
||||
class PushButton(QtWidgets.QPushButton):
|
||||
def __init__(self,parent = None):
|
||||
super(PushButton,self).__init__(parent)
|
||||
self.status = 0
|
||||
|
||||
def loadPixmap(self, pic_name):
|
||||
self.pixmap = QtGui.QPixmap(pic_name)
|
||||
self.btn_width = self.pixmap.width()/4
|
||||
self.btn_height = self.pixmap.height()
|
||||
self.setFixedSize(self.btn_width, self.btn_height)
|
||||
|
||||
def enterEvent(self,event):
|
||||
if not self.isChecked() and self.isEnabled():
|
||||
self.status = 1
|
||||
self.update()
|
||||
|
||||
def setDisabled(self, bool):
|
||||
super(PushButton,self).setDisabled(bool)
|
||||
if not self.isEnabled():
|
||||
self.status = 2
|
||||
self.update()
|
||||
else:
|
||||
self.status = 0
|
||||
self.update()
|
||||
|
||||
def mousePressEvent(self,event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
self.status = 2
|
||||
self.update()
|
||||
|
||||
def mouseReleaseEvent(self,event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
self.clicked.emit(True)
|
||||
if not self.isChecked():
|
||||
self.status = 3
|
||||
if self.menu():
|
||||
self.menu().exec_(event.globalPos())
|
||||
self.update()
|
||||
|
||||
def leaveEvent(self,event):
|
||||
if not self.isChecked() and self.isEnabled():
|
||||
self.status = 0
|
||||
self.update()
|
||||
|
||||
def paintEvent(self,event):
|
||||
self.painter = QtGui.QPainter()
|
||||
self.painter.begin(self)
|
||||
self.painter.drawPixmap(self.rect(), self.pixmap.copy(self.btn_width * self.status, 0, self.btn_width, self.btn_height))
|
||||
self.painter.end()
|
||||
|
||||
class PushButton2(QtWidgets.QPushButton):
|
||||
def __init__(self,parent = None):
|
||||
super(PushButton2,self).__init__(parent)
|
||||
|
||||
def loadPixmap(self, pic_name):
|
||||
self.pixmap = QtGui.QPixmap(pic_name)
|
||||
self.btn_width = self.pixmap.width()
|
||||
self.btn_height = self.pixmap.height()
|
||||
self.setFixedSize(self.btn_width, self.btn_height)
|
||||
|
||||
def paintEvent(self,event):
|
||||
self.painter = QtGui.QPainter()
|
||||
self.painter.begin(self)
|
||||
self.painter.drawPixmap(self.rect(), self.pixmap.copy(0, 0, self.btn_width, self.btn_height))
|
||||
self.painter.end()
|
||||
@@ -17,10 +17,10 @@ import sys
|
||||
import random
|
||||
import logging
|
||||
|
||||
from PyQt5.QtWidgets import (QAction, QAbstractItemView, QFrame, QHBoxLayout, QLabel, QMenu, QPushButton, QSlider, QTableWidget,
|
||||
QTableWidgetItem, QVBoxLayout, QApplication)
|
||||
from PyQt5.QtGui import QBrush, QColor, QCursor
|
||||
from PyQt5.QtCore import QUrl, Qt, QObject, QPropertyAnimation, QRect, QEasingCurve, QAbstractAnimation
|
||||
from PyQt5.QtWidgets import (QAction, QAbstractItemView, QDialog, QFrame, QHBoxLayout, QLabel, QMenu, QPushButton, QSlider,
|
||||
QTableWidget, QTableWidgetItem, QVBoxLayout, QApplication)
|
||||
from PyQt5.QtGui import QBrush, QColor, QCursor, QPixmap, QFont, QPainter, QPen, QLinearGradient
|
||||
from PyQt5.QtCore import QUrl, Qt, QObject, QPropertyAnimation, QPoint, QRect, QEasingCurve, QAbstractAnimation, QTime, QTimer, QRegExp, QRectF
|
||||
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QMediaMetaData, QMediaPlaylist
|
||||
|
||||
import addition
|
||||
@@ -33,6 +33,14 @@ from netEaseApi import netease
|
||||
from xiamiApi import xiami
|
||||
from qqApi import qqApi
|
||||
|
||||
# support desktop lyric.
|
||||
from desktopLyricButtons import *
|
||||
|
||||
def _fromUtf8(s):
|
||||
return s
|
||||
|
||||
QString = str
|
||||
# for desktop lyric too.
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -55,6 +63,9 @@ class PlayWidgets(QFrame):
|
||||
self.currentMusic = CurrentMusic(self)
|
||||
self.player = Player(self)
|
||||
|
||||
# test desktopLyric
|
||||
self.desktopLyric = DesktopLyric(self)
|
||||
|
||||
self.setButtons()
|
||||
self.setLabels()
|
||||
self.setSliders()
|
||||
@@ -597,6 +608,9 @@ class CurrentMusic(QFrame):
|
||||
|
||||
result = future.result().split('\n')
|
||||
|
||||
# set desktop lyric list.
|
||||
self.parent.player.lrc_lst = getLyric(result)
|
||||
|
||||
signal = self.parent.player.timeChanged
|
||||
|
||||
for x, i in enumerate(result):
|
||||
@@ -618,6 +632,7 @@ class CurrentMusic(QFrame):
|
||||
self.detailInfo.addLyricLabel(_LyricLabel(data[0][0], data[0][1], x, signal, self))
|
||||
|
||||
self.count = x
|
||||
|
||||
# 这边并不会返回添加了控件后的Value值。
|
||||
# self.sliderValue = self.detailInfo.maximumValue()
|
||||
# self.slideValue = round(self.sliderValue/x)
|
||||
@@ -650,6 +665,9 @@ class CurrentMusic(QFrame):
|
||||
lyricUrl = data['songs'][0].get('lyric')
|
||||
future = aAsync(xiami.lyric, lyricUrl)
|
||||
data = yield from future
|
||||
|
||||
self.lyricCache = data
|
||||
|
||||
return data
|
||||
|
||||
musicId = musicInfo.get('music_id')
|
||||
@@ -881,6 +899,9 @@ class Player(QMediaPlayer):
|
||||
# 默认列表循环。
|
||||
self.playList.setPlaybackMode(self.playList.Loop)
|
||||
|
||||
# lyric from CurrentMusic setting.
|
||||
self.lrc_lst = []
|
||||
|
||||
self.setConnects()
|
||||
|
||||
# 功能。
|
||||
@@ -992,7 +1013,7 @@ class Player(QMediaPlayer):
|
||||
self.playWidgets.countTime.setText(self.transTime(self.musicTime))
|
||||
self.playList.duration = duration
|
||||
|
||||
def positionChangedEvent(self):
|
||||
def positionChangedEvent(self, position):
|
||||
"""音乐在Media里以毫秒的形式保存,这里是播放时的进度条。"""
|
||||
currentTime = self.position()/1000
|
||||
transedTime = self.transTime(currentTime)
|
||||
@@ -1004,12 +1025,51 @@ class Player(QMediaPlayer):
|
||||
return
|
||||
# *1000是为了与进度条的范围相匹配。
|
||||
self.playWidgets.slider.setValue(currentTime/self.musicTime*1000)
|
||||
self.setLyricEvent(position)
|
||||
|
||||
def stateChangedEvent(self):
|
||||
""""""
|
||||
if self.state() == 0 and self.playList.mediaCount() == 0 and self.playWidgets.pauseButton.isVisible():
|
||||
self.playWidgets.stopEvent(self)
|
||||
|
||||
def setLyricEvent(self, position):
|
||||
# copy from https://github.com/wn0112/PPlayer
|
||||
t = QTime(0, 0, 0)
|
||||
t = t.addMSecs(int(position))
|
||||
lycF = ''
|
||||
lycL = ''
|
||||
lycM = ''
|
||||
if self.lrc_lst:
|
||||
lenOfLrc = len(self.lrc_lst)
|
||||
for i in range(lenOfLrc):
|
||||
if t.toString("mm:ss") in self.lrc_lst[i][0]:
|
||||
t1 = t
|
||||
if i < lenOfLrc - 1:
|
||||
x = self.lrc_lst[i+1][0].replace('[', '')
|
||||
x = x.replace(']', '')
|
||||
t1 = QTime().fromString(x, 'mm:ss.z')
|
||||
intervel = t.msecsTo(t1)
|
||||
else:
|
||||
t1 = QTime().fromString('00:10.99')
|
||||
intervel = 3000
|
||||
self.parent.desktopLyric.stopMask()
|
||||
self.parent.desktopLyric.setText(self.lrc_lst[i][1], intervel)
|
||||
self.parent.desktopLyric.startMask()
|
||||
if i > 0:
|
||||
lycM = self.lrc_lst[i-1][1]
|
||||
j = 0
|
||||
while(j < i-1):
|
||||
lycF += self.lrc_lst[j][1]+'\n'
|
||||
j += 1
|
||||
j = i
|
||||
while(j < lenOfLrc - 1):
|
||||
lycL += self.lrc_lst[j+1][1]+'\n'
|
||||
j += 1
|
||||
# self.parent.desktopLyric.setText(lycF, lycM, self.lrc_lst[i][1], lycL, intervel)
|
||||
# self.parent.desktopLyric.setText(lycF, lycM, self.lrc_lst[i][1], lycL, intervel)
|
||||
|
||||
break
|
||||
|
||||
# def mediaStatusChangedEvent(self, status):
|
||||
""""""
|
||||
# 8是无效音频。
|
||||
@@ -1400,6 +1460,226 @@ class _LyricLabel(QLabel):
|
||||
self.setText(self.myLyric)
|
||||
|
||||
|
||||
class DesktopLyric(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super(DesktopLyric, self).__init__()
|
||||
self.lyric = QString('Lyric Show.')
|
||||
self.intervel = 0
|
||||
self.maskRect = QRectF(0, 0, 0, 0)
|
||||
self.maskWidth = 0
|
||||
self.widthBlock = 0
|
||||
self.t = QTimer()
|
||||
self.screen = QApplication.desktop().availableGeometry()
|
||||
self.setObjectName(_fromUtf8("Dialog"))
|
||||
self.setWindowFlags(Qt.CustomizeWindowHint | Qt.FramelessWindowHint | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.Tool)
|
||||
self.setMinimumHeight(65)
|
||||
self.setAttribute(Qt.WA_TranslucentBackground)
|
||||
self.handle = lyric_handle(self)
|
||||
self.verticalLayout = QVBoxLayout(self)
|
||||
self.verticalLayout.setSpacing(0)
|
||||
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
|
||||
self.font = QFont(_fromUtf8('微软雅黑, verdana'), 50)
|
||||
self.font.setPixelSize(50)
|
||||
# QMetaObject.connectSlotsByName(self)
|
||||
self.handle.lyricmoved.connect(self.newPos)
|
||||
self.t.timeout.connect(self.changeMask)
|
||||
|
||||
def changeMask(self):
|
||||
self.maskWidth += self.widthBlock
|
||||
self.update()
|
||||
|
||||
def setText(self, s, intervel=0):
|
||||
self.lyric = s
|
||||
self.intervel = intervel
|
||||
self.maskWidth = 0
|
||||
self.update()
|
||||
|
||||
def hideLyric(self):
|
||||
self.hide()
|
||||
# self.emit(SIGNAL('lyrichide()'))
|
||||
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.handle.leaveEvent(event)
|
||||
|
||||
def show(self):
|
||||
|
||||
super(DesktopLyric, self).show()
|
||||
|
||||
def hide(self):
|
||||
super(DesktopLyric, self).hide()
|
||||
self.handle.hide()
|
||||
|
||||
def enterEvent(self, event):
|
||||
pass
|
||||
# self.handle.handler.setFocus()
|
||||
# self.handle.show()
|
||||
|
||||
def newPos(self, p):
|
||||
self.move(self.pos().x() + p.x(), self.pos().y() + p.y())
|
||||
# self.move(500, 600)
|
||||
|
||||
|
||||
def startMask(self):
|
||||
self.t.start(100)
|
||||
|
||||
def stopMask(self):
|
||||
self.t.stop()
|
||||
self.update()
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
painter.setFont(self.font)
|
||||
|
||||
linear = QLinearGradient(QPoint(self.rect().topLeft()), QPoint(self.rect().bottomLeft()))
|
||||
linear.setStart(0, 10)
|
||||
linear.setFinalStop(0, 50)
|
||||
linear.setColorAt(0.1, QColor(14, 179, 255));
|
||||
linear.setColorAt(0.5, QColor(154, 232, 255));
|
||||
linear.setColorAt(0.9, QColor(14, 179, 255));
|
||||
|
||||
linear2 = QLinearGradient(QPoint(self.rect().topLeft()), QPoint(self.rect().bottomLeft()))
|
||||
linear2.setStart(0, 10)
|
||||
linear2.setFinalStop(0, 50)
|
||||
linear2.setColorAt(0.1, QColor(222, 54, 4));
|
||||
linear2.setColorAt(0.5, QColor(255, 172, 116));
|
||||
linear2.setColorAt(0.9, QColor(222, 54, 4));
|
||||
|
||||
painter.setPen(QColor(0, 0, 0, 200));
|
||||
painter.drawText(QRect(1, 1, self.screen.width(), 60), Qt.AlignHCenter | Qt.AlignVCenter, self.lyric)
|
||||
|
||||
painter.setPen(QColor('transparent'));
|
||||
self.textRect = painter.drawText(QRect(0, 0, self.screen.width(), 60), Qt.AlignHCenter | Qt.AlignVCenter, self.lyric)
|
||||
|
||||
painter.setPen(QPen(linear, 0))
|
||||
painter.drawText(self.textRect, Qt.AlignLeft | Qt.AlignVCenter, self.lyric)
|
||||
if self.intervel != 0:
|
||||
self.widthBlock = self.textRect.width()/(self.intervel/150.0)
|
||||
else:
|
||||
self.widthBlock = 0
|
||||
self.maskRect = QRectF(self.textRect.x(), self.textRect.y(), self.textRect.width(), self.textRect.height())
|
||||
self.maskRect.setWidth(self.maskWidth)
|
||||
painter.setPen(QPen(linear2, 0));
|
||||
painter.drawText(self.maskRect, Qt.AlignLeft | Qt.AlignVCenter, self.lyric)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.buttons() == Qt.LeftButton:
|
||||
self.m_drag = True
|
||||
self.m_DragPosition = event.globalPos()-self.pos()
|
||||
event.accept()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
try:
|
||||
if event.buttons() and Qt.LeftButton:
|
||||
self.move(event.globalPos()-self.m_DragPosition)
|
||||
event.accept()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.buttons() == Qt.LeftButton:
|
||||
self.m_drag = False
|
||||
|
||||
|
||||
class lyric_handle(QDialog):
|
||||
# copy from https://github.com/wn0112/PPlayer
|
||||
lyricmoved = pyqtSignal(QPoint)
|
||||
def __init__(self, parent=None):
|
||||
super(lyric_handle, self).__init__(parent)
|
||||
self.timer = QTimer()
|
||||
self.setObjectName(_fromUtf8("Dialog"))
|
||||
self.setWindowFlags(Qt.CustomizeWindowHint | Qt.FramelessWindowHint | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.Tool)
|
||||
self.setStyleSheet('QDialog { background: #2c7ec8; border: 0px solid black;}')
|
||||
self.horiLayout = QHBoxLayout(self)
|
||||
self.horiLayout.setSpacing(5)
|
||||
self.horiLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.horiLayout.setObjectName(_fromUtf8("horiLayout"))
|
||||
self.handler = QLabel(self)
|
||||
self.handler.setToolTip('Move Lyric')
|
||||
self.handler.setPixmap(QPixmap(':/icons/handler.png'))
|
||||
self.handler.setMouseTracking(True)
|
||||
self.lockBt = PushButton2(self)
|
||||
self.lockBt.setToolTip('Unlocked')
|
||||
self.lockBt.loadPixmap(QPixmap(':/icons/unlock.png'))
|
||||
self.hideBt = PushButton2(self)
|
||||
self.hideBt.setToolTip('Hide Lyric')
|
||||
self.hideBt.loadPixmap(QPixmap(':/icons/close.png').copy(48, 0, 16, 16))
|
||||
self.lockBt.setCheckable(True)
|
||||
|
||||
self.horiLayout.addWidget(self.handler)
|
||||
self.horiLayout.addWidget(self.lockBt)
|
||||
self.horiLayout.addWidget(self.hideBt)
|
||||
|
||||
self.lockBt.clicked.connect(self.lockLyric)
|
||||
self.hideBt.clicked.connect(self.hideLyric)
|
||||
self.timer.timeout.connect(self.hide)
|
||||
|
||||
def lockLyric(self):
|
||||
if self.lockBt.isChecked():
|
||||
self.lockBt.loadPixmap(QPixmap(':/icons/lock.png'))
|
||||
self.lockBt.setToolTip('Locked')
|
||||
self.lockBt.update()
|
||||
else:
|
||||
self.lockBt.loadPixmap(QPixmap(':/icons/unlock.png'))
|
||||
self.lockBt.setToolTip('Unlocked')
|
||||
self.lockBt.update()
|
||||
|
||||
def hideLyric(self):
|
||||
# self.parent().emit(SIGNAL('lyrichide()'))
|
||||
self.parent().lyrichide.emit()
|
||||
self.parent().hide()
|
||||
self.hide()
|
||||
|
||||
def isInTitle(self, xPos, yPos):
|
||||
if self.lockBt.isChecked():
|
||||
return False
|
||||
else:
|
||||
return yPos <= self.height() and 0 <= xPos <= self.handler.width()
|
||||
|
||||
def moveEvent(self, event):
|
||||
# self.emit(SIGNAL("lyricmoved(QPoint)"), event.pos() - event.oldPos())
|
||||
self.lyricmoved.emit(QPoint(event.pos() - event.oldPos()))
|
||||
|
||||
def enterEvent(self, event):
|
||||
# print(1)
|
||||
self.setFocus()
|
||||
self.timer.stop()
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.timer.stop()
|
||||
self.timer.start(3000)
|
||||
|
||||
|
||||
def getLyric(rawLyric):
|
||||
# copu from https://github.com/wn0112/PPlayer
|
||||
lrc = rawLyric
|
||||
|
||||
r1 = re.compile("\[(\d{2}:\d{2}(.\d+)?)\]")
|
||||
r2 = re.compile("\[\d+:+.+\](.*)")
|
||||
r3 = re.compile("\[offset:(-?\d+)\]")
|
||||
item = []
|
||||
lrc_lst = []
|
||||
offset = 0
|
||||
for line in lrc:
|
||||
times = r1.findall(line)
|
||||
lrc_words = r2.findall(line)
|
||||
if lrc_words:
|
||||
lrc_words = lrc_words[0]
|
||||
else:
|
||||
lrc_words = []
|
||||
|
||||
if len(lrc_words) and lrc_words[0].rstrip():
|
||||
for i in times:
|
||||
item.append(i[0])
|
||||
item.append(lrc_words)
|
||||
lrc_lst.append(item)
|
||||
item = []
|
||||
lrc_lst.sort()
|
||||
|
||||
return lrc_lst
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
os.chdir('..')
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
## 2018/01/06 更新: <br />
|
||||
以下功能为测试版:
|
||||
0. 新增桌面歌词,目前不能取消。
|
||||
1. 新增歌单内歌曲/搜索的歌曲下载。下载后在当前目录的downloads文件夹。
|
||||
|
||||
BUG修复:
|
||||
0. 现在获取歌曲信息失败的本地音乐只显示文件名不显示歌手信息,播放方面不影响。
|
||||
|
||||
其他:
|
||||
0. 新增异步方法的报错调试日志。
|
||||
|
||||
感谢小姐姐提供~。
|
||||
桌面歌词感谢<a href="https://github.com/wn0112/">wn0112</a>.
|
||||
|
||||
## 2018/01/03 更新: <br />
|
||||
0. 新增本地音乐歌词显示。
|
||||
1. 修复一些原本有歌词的虾米音乐显示不了歌词的情况。
|
||||
|
||||
Reference in New Issue
Block a user