class PlaylistButton(QPushButton): # parent = navigation __solts__ = ('parent', 'grandparent', 'ids', 'coverImgUrl', 'catch', 'detailFrame', 'result', 'singsIds', 'singsUrls') hasClicked = pyqtSignal(int, str) def __init__(self, parent, ids, coverImgUrl, *args): super(PlaylistButton, self).__init__(*args) self.parent = parent self.grandparent = self.parent.parent self.setCheckable(True) self.setAutoExclusive(True) self.ids = ids self.coverImgUrl = coverImgUrl self.catch = None self.result = None self.singsIds = None self.singsUrls = None self.clicked.connect(self.clickedEvent) def clickedEvent(self): self.hasClicked.emit(self.ids, self.coverImgUrl)
class OneSing(QFrame): # 大量创建,这样可以省内存。 __solts__ = ('parent', 'ggparent', 'detailFrame', 'row', 'column', 'ids', 'picName', 'picLabel', 'nameLabel', 'mainLayout', 'mousePos', 'result', 'catch', 'singsIds', 'singsUrls') clicked = pyqtSignal(int, str) def __init__(self, row, column, ids=None, parent=None, picName=None): super(OneSing, self).__init__() self.setObjectName('oneSing') # 自己的位置信息。 self.row = row self.column = column # 歌单号。 self.ids = ids # 大图的缓存名。 self.picName = picName self.setMinimumSize(180, 235) self.picLabel = QLabel() self.picLabel.setObjectName('picLabel') self.picLabel.setMinimumSize(180, 180) self.picLabel.setMaximumSize(180, 180) self.nameLabel = QLabel() self.nameLabel.setMaximumWidth(180) self.nameLabel.setWordWrap(True) self.mainLayout = QVBoxLayout(self) self.mainLayout.addWidget(self.picLabel) self.mainLayout.addWidget(self.nameLabel) # 功能。 def setStyleSheets(self, styleSheet=None): if styleSheet: self.setStyleSheet(styleSheet) # 事件。 def mousePressEvent(self, event): # 记录下当前鼠标的位置。 self.mousePos = QCursor.pos() def mouseReleaseEvent(self, event): # 先进行判断,防止误点将鼠标移开后还是会判断为已经点击的尴尬。 if QCursor.pos() != self.mousePos: return else: self.clicked.emit(self.ids, self.picName)
class PlaylistButton(QPushButton): """ 提供一个简单点击的歌单自动切换点击状态,点击时会发出hasClicked信号。 Args: parent 父类。 ids 歌单的ids。 coverImgUrl 这个歌单应该包含的图片地址 -> None, url. *args其他作用于原生Button的参数。 singsIds 与 singsUrls暂时无用,不提供接受接口。 """ __solts__ = ('parent', 'grandparent', 'ids', 'coverImgUrl', 'catch', 'detailFrame', 'result', 'singsIds', 'singsUrls') hasClicked = pyqtSignal(int, str) def __init__(self, parent, ids, coverImgUrl, *args): super(PlaylistButton, self).__init__(*args) self.parent = parent self.grandparent = self.parent.parent self.setCheckable(True) self.setAutoExclusive(True) self.ids = ids self.coverImgUrl = coverImgUrl self.catch = None self.result = None self.singsIds = None self.singsUrls = None self.clicked.connect(self.clickedEvent) def clickedEvent(self): self.hasClicked.emit(self.ids, self.coverImgUrl)
class Player(QMediaPlayer): """播放器组件。""" timeChanged = pyqtSignal(str) def __init__(self, parent=None): super(Player, self).__init__() self.setObjectName('player') self.parent = parent self.playWidgets = self.parent self.transTime = addition.itv2time self.musicTime = 0 self.playList = _MediaPlaylist(self) # 默认列表循环。 self.playList.setPlaybackMode(self.playList.Loop) self.setConnects() # 功能。 def setConnects(self): """用于设置连接的信号槽。""" self.durationChanged.connect(self.countTimeEvent) self.positionChanged.connect(self.positionChangedEvent) self.stateChanged.connect(self.stateChangedEvent) self.error.connect(self.dealError) self.playList.setInitConnection() # self.currentMediaChanged.connect(self.currentMediaChangedEvent) # self.mediaStatusChanged.connect(self.mediaStatusChangedEvent) def setDisconnects(self): self.durationChanged.disconnect() self.positionChanged.disconnect() self.stateChanged.disconnect() self.error.disconnect() self.mediaStatusChanged.disconnect() # self.playList.setInitConnection() def setMusic(self, url, data): """设置当前的音乐,可用直接用网络链接。""" if url: if 'http' in url or 'file' in url: self.playList.addMedias(QMediaContent(QUrl(url)), data) else: self.playList.addMedias(QMediaContent(QUrl.fromLocalFile(url)), data) return True else: return False # self.playMusic() def setAllMusics(self, datas): pass self.playList.addAllMedias(datas) def setIndex(self, index): self.playList.setCurrentIndex(index) # self.playMusic() def allTime(self): """返回当前音乐的总时间。(秒)""" return self.duration() / 1000 def getCurrentMusicInfo(self): return self.playList.mediaList.get( self.currentMedia().canonicalUrl().toString()) def playMusic(self, url=None, data=None): """播放音乐。""" if url: self.setMusic(url, data) # 播放事件。 self.playWidgets.playEvent(self) else: self.playWidgets.playEvent(self) def dealError(self, error): # 具体内容看文档: # http://doc.qt.io/qt-5/qmediaplayer.html if error: musicInfo = self.playList.mediaList.pop( self.currentMedia().canonicalUrl().toString()) self.loadRealMusicUrl(musicInfo) @toTask def loadRealMusicUrl(self, musicInfo): # 如果有个Url出错,比如音乐地址403重新获取下地址。 musicId = musicInfo.get('music_id') # invalidUrl = musicInfo.get('url') if not musicId: self.playWidgets.nextSing() return future = aAsync(api.singsUrl, [musicId]) data = yield from future if not data: self.playWidgets.nextSing() return url = data[0]['url'] musicInfo['url'] = url self.playList.mediaList[url] = musicInfo # replaceIndex = self.playList.musics.index(invalidUrl) self.playList.musics[self.playList.currentIndex] = url self.playList.play() def saveCookies(self): self.playList.saveCookies() def loadCookies(self): self.playList.loadCookies() # 事件。 def countTimeEvent(self, duration): """总时间改变的事件。相当于加载完成歌曲的事件。""" self.musicTime = duration / 1000 # print('COUNT{0}'.format(duration)) self.playWidgets.countTime.setText(self.transTime(self.musicTime)) self.playList.duration = duration def positionChangedEvent(self): """音乐在Media里以毫秒的形式保存,这里是播放时的进度条。""" currentTime = self.position() / 1000 transedTime = self.transTime(currentTime) self.playWidgets.currentTime.setText(transedTime) self.timeChanged.emit(transedTime) # print(self.transTime(currentTime)) # position先于duration变化,会出现/0的情况。 if self.musicTime == 0: return # *1000是为了与进度条的范围相匹配。 self.playWidgets.slider.setValue(currentTime / self.musicTime * 1000) def stateChangedEvent(self): """""" if self.state() == 0 and self.playList.mediaCount( ) == 0 and self.playWidgets.pauseButton.isVisible(): self.playWidgets.stopEvent(self) # def mediaStatusChangedEvent(self, status): """"""
class ConfigDetailSings(QObject): download = pyqtSignal(dict) def __init__(self, parent=None): super(ConfigDetailSings, self).__init__() self.detailSings = parent self.musicList = [] self.currentIndex = 0 self.grandparent = self.detailSings.parent self.player = self.grandparent.playWidgets.player self.playList = self.grandparent.playWidgets self.currentMusic = self.grandparent.playWidgets.currentMusic self.transTime = transTime self.detailSings.singsTable.contextMenuEvent = self.singsFrameContextMenuEvent self.bindConnect() self.setContextMenu() def bindConnect(self): self.detailSings.playAllButton.clicked.connect( self.addAllMusicToPlayer) self.detailSings.singsTable.itemDoubleClicked.connect( self.itemDoubleClickedEvent) def setContextMenu(self): 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 self.download.emit(musicInfo) def addAllMusicToPlayer(self): self.playList.setPlayerAndPlaylists(self.musicList) def setupDetailFrames(self, datas, singsUrls, singsIds): result = datas self.musicList = [] self.detailSings.singsTable.clearContents() self.detailSings.titleLabel.setText(result['name']) self.detailSings.authorName.setText(result['creator']['nickname']) description = result['description'] # 有些没有简介会报错的。 if not description: description = '' self.detailSings.descriptionText.setText(description) # 这边添加歌曲的信息到table。 self.detailSings.singsTable.setRowCount(result['trackCount']) for i, j, t, x in zip(result['tracks'], range(result['trackCount']), singsUrls, singsIds): names = i['name'] musicName = QTableWidgetItem(names) self.detailSings.singsTable.setItem(j, 0, musicName) author = i['artists'][0]['name'] musicAuthor = QTableWidgetItem(author) self.detailSings.singsTable.setItem(j, 1, musicAuthor) times = self.transTime(i['duration'] / 1000) musicTime = QTableWidgetItem(times) self.detailSings.singsTable.setItem(j, 2, musicTime) music_img = i['album']['blurPicUrl'] lyric = i.get('lyric') self.musicList.append({ 'url': t, 'name': names, 'time': times, 'author': author, 'music_img': music_img, 'music_id': x, 'lyric': lyric }) # 事件。 def itemDoubleClickedEvent(self): currentRow = self.detailSings.singsTable.currentRow() data = self.musicList[currentRow] self.playList.setPlayerAndPlayList(data) def singsFrameContextMenuEvent(self, event): item = self.detailSings.singsTable.itemAt( self.detailSings.singsTable.mapFromGlobal(QCursor.pos())) self.menu = QMenu(self.detailSings.singsTable) self.menu.addAction(self.actionNextPlay) self.menu.addAction(self.actionDownloadSong) try: self.currentIndex = item.row() - 1 # 在索引是最后一行时会获取不到。 except: self.currentIndex = -1 self.menu.exec_(QCursor.pos())
class ConfigSearchArea(QObject): download = pyqtSignal(dict) 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 # get storage folder self.downloadFolder = self.searchArea.parent.config.getDownloadFolder() 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 useless, 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 self.download.emit(musicInfo) def searchBy(self, index): currentWidgetName = self.searchArea.contentsTab.tabText(index) self.currentName = currentWidgetName self.search(currentWidgetName) @toTask def search(self, name): """接受name信息,由这个引擎进行搜索。""" searchEngineer = self.searchEngineers[name] data = yield from aAsync(searchEngineer.search, self.searchArea.text) if not data['songCount']: songsIds = [] data['songs'] = [] else: songsIds = [i['id'] for i in data['songs']] if name == '网易云': songsDetail = {i: 'http' for i in songsIds} elif name == '虾米' or name == 'QQ': songsDetail = {i['id']: i['mp3Url'] for i in data['songs']} # 进行重新编辑方便索引。 songs = data['songs'] data['songs'] = [{ 'name': i['name'], 'artists': i['ar'], 'picUrl': i['al']['picUrl'], 'mp3Url': songsDetail[i['id']], 'duration': i['dt'], 'music_id': i['id'], 'lyric': i.get('lyric') } for i in songs] songsCount = data['songCount'] # 总数是0即没有找到。 if not songsCount: songs = [] else: songs = data['songs'] self.setSingsData(songs) def setSingsData(self, data): # 单曲搜索结果。 searchArea = self.searchArea.contentsTab.currentWidget() if not len(data): # self.contentsTab.addTab() searchArea.noSingsContentsLabel.setText( self.noContents.format(self.searchArea.text, '单曲')) searchArea.singsResultTable.hide() searchArea.noSingsContentsLabel.show() else: searchArea.singsResultTable.setRowCount(len(data)) musicList = [] for count, datas in enumerate(data): picUrl = datas['picUrl'] url = datas['mp3Url'] name = datas['name'] authors = ','.join([t['name'] for t in datas['artists']]) duration = self.transTime(datas['duration'] / 1000) musicId = datas['music_id'] searchArea.singsResultTable.setItem(count, 0, QTableWidgetItem(name)) searchArea.singsResultTable.setItem(count, 1, QTableWidgetItem(authors)) searchArea.singsResultTable.setItem(count, 2, QTableWidgetItem(duration)) musicList.append({ 'url': url, 'name': name, 'time': duration, 'author': authors, 'music_img': picUrl, 'music_id': musicId }) searchArea.noSingsContentsLabel.hide() searchArea.singsResultTable.show() self.musicList = musicList def itemDoubleClickedEvent(self): currentRow = self.searchArea.contentsTab.currentWidget( ).singsResultTable.currentRow() 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())