├── README.md ├── audio.py ├── form.py ├── main.py ├── resources ├── default.png ├── file.png ├── head.png ├── left.png ├── list.png ├── off.png ├── on.png ├── pause.png ├── play.png └── right.png └── run.py /README.md: -------------------------------------------------------------------------------- 1 | # PyQt5音乐播放器 2 | 3 | ## 项目简介 4 | 这是一个使用PyQt5构建的跨平台音乐播放器,集成了Pygame音频回放和eyed3元数据解析功能。 5 | (由于各个式解析方案不同,目前仅开发支持MP3格式,后续可能会补充) 6 | 7 | ![Image](https://github.com/aki-zone/pic-repo/blob/main/img/20241127202722.png?raw=true) 8 | ![Image](https://github.com/aki-zone/pic-repo/blob/main/img/20241127202750.png?raw=true) 9 | 10 | 11 | 12 | ## 特性 13 | - 音量调节 14 | - 播放列表管理 15 | - 音频目录选择 16 | - 歌手/时长等信息展示 17 | - 专辑封面展示 18 | - 播放/暂停控制 19 | - 上一首/下一首 20 | - 有序/随机/单曲循环播放 21 | - 重启后按配置文件恢复上一次状态 22 | 23 | ## 项目结构 24 | ``` 25 | music-player/ 26 | │ 27 | ├── audio.py # 音频处理核心逻辑 28 | ├── form.py # 界面设计 29 | ├── main.py # 主程序入口 30 | ├── run.py # 启动脚本 31 | └── resources/ # icon资源文件夹 32 | ``` 33 | 34 | ## 开发环境 35 | - Python 3.x 36 | - PyQt5 37 | - Pygame 38 | - eyed3 39 | 40 | ## 运行方式 41 | ```bash 42 | python run.py 43 | ``` 44 | 45 | ## 版权声明 46 | 47 | 本项目采用 [MIT 开源许可证](https://opensource.org/licenses/MIT) 进行许可。 48 | 49 | 版权所有 © [0xAki],2023。保留所有权利。 50 | 51 | -------------------------------------------------------------------------------- /audio.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import os 4 | import sys 5 | import threading 6 | from typing import List 7 | from pydub import AudioSegment 8 | import eyed3 9 | import pygame 10 | from pydub.utils import mediainfo 11 | 12 | 13 | class PlayBean: 14 | """ 15 | 音乐播放器,根据地址、音量、时间戳播放音乐文件 16 | """ 17 | def __init__(self, start_time=0, volume=0): 18 | self.start_time = start_time # 用于跟踪当前播放的时间 19 | self.playing = False # 用于跟踪音乐播放状态 20 | self.volume = volume 21 | 22 | def play_music(self, start_time, volume, file_path): 23 | 24 | pygame.init() 25 | pygame.mixer.init() 26 | pygame.mixer.music.load(file_path) 27 | 28 | self.playing = True # 设置播放状态为True 29 | self.start_time = start_time 30 | self.volume = volume 31 | self.music_length = pygame.mixer.Sound(file_path).get_length() 32 | 33 | 34 | def play_thread(): 35 | """ 36 | 这里需要多线程操作以供播放器在播放音乐的同时可以进行其他任务 37 | """ 38 | 39 | # 设置音量 40 | pygame.mixer.music.set_volume(self.volume / 100) 41 | 42 | # 开始播放 43 | pygame.mixer.music.play() 44 | 45 | # 设置开始播放的时间 46 | if self.start_time > self.music_length: 47 | self.start_time = 0 48 | pygame.mixer.music.set_pos(self.start_time) 49 | 50 | # 循环检测音乐是否播放结束 51 | clock = pygame.time.Clock() 52 | 53 | pin = 0 54 | while pygame.mixer.music.get_busy() and self.start_time < self.music_length: 55 | self.start_time += (pygame.mixer.music.get_pos() / 1000 - pin) 56 | # 设置音量 57 | pygame.mixer.music.set_volume(self.volume / 100) 58 | self.playing = True 59 | pin = pygame.mixer.music.get_pos() / 1000 60 | clock.tick(900) # 控制循环速度,避免过度占用CPU 61 | 62 | # 播放结束,更新 playing 63 | self.playing = False # 播放结束,将播放状态设为False 64 | 65 | # 创建线程并启动 66 | playThread = threading.Thread(target=play_thread) 67 | playThread.start() 68 | 69 | def pause_music(self): # 暂停 70 | pygame.mixer.music.pause() 71 | self.playing = False # 暂停后将播放状态设为False 72 | 73 | def stop_music(self): # 停止 74 | pygame.mixer.music.stop() 75 | self.start_time = 0 76 | self.playing = False # 停止后将播放状态设为False 77 | 78 | def is_busy(self): # 判断音乐器是否在播放中 79 | return self.playing 80 | 81 | def get_len(self): # 返回音乐长度 82 | return self.music_length 83 | 84 | def set_start(self, value): # 设置全局音乐起始播放时间戳 85 | self.start_time = value 86 | 87 | def set_volume(self, value): # 设置全局音乐起始播放音量值 88 | self.volume = value 89 | 90 | 91 | class MusicReader: 92 | 93 | """ 94 | 获取音乐文件信息,包括但不限于:标题,艺术家,专辑名,大小,时长,地址,封面二进制流,MIME 95 | """ 96 | def __init__(self, file_path): 97 | self.audiofile = "" 98 | self.album_MIME = "" 99 | self.album_cover = None 100 | self.filename = "" 101 | self.size_bytes = 0 102 | self.album = "" 103 | self.artist = "" 104 | self.title = "" 105 | self.music_length = 100 106 | self.file_path = file_path 107 | 108 | # 根据文件后缀选择解析库 109 | file_extension = os.path.splitext(self.file_path)[1].lower() 110 | 111 | # TODO 暂时仅完成了mp3格式的解析 112 | if file_extension == '.mp3': 113 | self.load_mp3_info() 114 | 115 | # elif file_extension == '.flac': 116 | # self.load_flac_info() 117 | 118 | def to_dict(self): 119 | return { 120 | 'file_path': self.file_path, 121 | 'title': self.title, 122 | 'artist': self.artist, 123 | 'album': self.album, 124 | 'size_bytes': self.size_bytes, 125 | 'filename': self.filename, 126 | 'album_cover': base64.b64encode(self.album_cover).decode('utf-8') if self.album_cover else None, 127 | 'album_MIME': self.album_MIME, 128 | 'music_length': self.music_length 129 | } 130 | 131 | def import_music_info_from_json(self, json_data: str): 132 | """ 133 | json导入函数 134 | """ 135 | data = json.loads(json_data) 136 | self.title = data.get('title', "") 137 | self.artist = data.get('artist', "") 138 | self.album = data.get('album', "") 139 | self.size_bytes = data.get('size_bytes', None) 140 | self.filename = data.get('filename', "") 141 | 142 | album_cover_str = data.get('album_cover') 143 | self.album_cover = base64.b64decode(album_cover_str) if album_cover_str else None 144 | 145 | self.album_MIME = data.get('album_MIME', "") 146 | self.music_length = data.get('music_length', None) 147 | 148 | # ------------------调试类函数------------------------ 149 | 150 | def display_metadata(self): 151 | """ 152 | 全信息输出函数 153 | """ 154 | print(f"标题: {self.title}") 155 | print(f"艺术家: {self.artist}") 156 | print(f"专辑: {self.album}") 157 | print(f"大小 (bytes): {self.size_bytes}") 158 | print(f"文件名: {self.filename}") 159 | print(f"MIME: {self.album_MIME}") 160 | print(f"音乐总时长 (毫秒): {self.music_length}") 161 | if self.album_cover: 162 | print("专辑封面: 存在") 163 | print(self.album_cover) 164 | else: 165 | print("专辑封面: 空白") 166 | 167 | def get_info_str(self) -> str: 168 | """ 169 | 全信息转为str格式函数 170 | """ 171 | metadata_str = f"标题: {self.title}\n" 172 | metadata_str += f"艺术家: {self.artist}\n" 173 | metadata_str += f"专辑: {self.album}\n" 174 | metadata_str += f"大小: {self.size_bytes / (1024 * 1024):.2f} (MB)\n" 175 | metadata_str += f"文件名: {self.filename}\n" 176 | metadata_str += f"MIME: {self.album_MIME}\n" 177 | metadata_str += f"音乐总时长: {self.music_length / 1000} (秒)\n" 178 | return metadata_str 179 | 180 | def load_mp3_info(self): 181 | """ 182 | 获取mp3文件信息函数,这里使用eyed3作为解析库 183 | """ 184 | self.audiofile = eyed3.load(self.file_path) 185 | if self.audiofile.tag: 186 | # 读取元信息 187 | self.title = self.audiofile.tag.title 188 | self.artist = self.audiofile.tag.artist 189 | self.album = self.audiofile.tag.album 190 | self.size_bytes = self.audiofile.info.size_bytes 191 | self.filename = self.audiofile.path 192 | 193 | # 读取专辑封面 194 | if self.audiofile.tag.images: 195 | self.album_cover = self.audiofile.tag.images[0].image_data 196 | self.album_MIME = self.audiofile.tag.images[0].mime_type 197 | else: 198 | self.title = None 199 | self.artist = None 200 | self.album = None 201 | self.size_bytes = None 202 | self.filename = None 203 | self.album_cover = None 204 | self.album_MIME = None 205 | 206 | # 获取音乐时长(单位:毫秒) 207 | self.music_length = int(self.audiofile.info.time_secs * 1000) if self.audiofile.info else None 208 | 209 | # TODO 关于flac格式解析算法暂时无法完成 210 | def load_flac_info(self): 211 | pass 212 | 213 | 214 | class Config: 215 | """ 216 | 单元配置函数,储存当前音乐文件信息,以及其被预设好、操作过的各种播放信息 217 | 例如:播放时间点,播放音量,归属目录地址... 218 | """ 219 | def __init__(self, music_list: List[str] = None, volume: int = 50, music_url: str = "", 220 | music_now: float = 0.0, music_info: MusicReader = None, music_dir: str = ""): 221 | self.music_list = music_list 222 | self.music_url = music_url 223 | self.volume = volume 224 | self.music_now = music_now 225 | self.music_dir = music_dir 226 | 227 | if music_info is None and music_url is not None: 228 | self.music_info = MusicReader(music_url) 229 | else: 230 | self.music_info = music_info 231 | 232 | def set_volume(self, volume: int): 233 | self.volume = volume 234 | 235 | def set_music_url(self, music_url: str): 236 | self.music_url = music_url 237 | 238 | def set_music_now(self, music_now: float): 239 | self.music_now = music_now 240 | 241 | def set_music_info(self, music_info: MusicReader): 242 | self.music_info = music_info 243 | 244 | def set_music_dir(self, music_dir: str): 245 | self.music_dir = music_dir 246 | 247 | def import_config_from_json(self, json_data: str): 248 | """ 249 | json导入函数 250 | """ 251 | data = json.loads(json_data) 252 | 253 | self.music_list = data.get('music_list', []) 254 | self.volume = data.get('volume', 50) 255 | self.music_url = data.get('music_url', None) 256 | self.music_dir = data.get('music_dir', None) 257 | self.music_now = data.get('music_now', 0.0) 258 | 259 | music_info_data = data.get('music_info', {}) 260 | music_reader = MusicReader('') 261 | music_reader.import_music_info_from_json(json.dumps(music_info_data)) 262 | 263 | self.music_info = music_reader 264 | 265 | def save_config_file(self, filename="config.json"): 266 | """ 267 | 保存json文件,以供下次启动程序时初始化。这里可以用全局变量,暂时没做修改 268 | """ 269 | config_data = self.export_config_to_json() 270 | with open(filename, 'w') as file: 271 | file.write(config_data) 272 | 273 | def export_config_to_json(self) -> str: 274 | """ 275 | 导出信息数据为json格式字典 276 | """ 277 | data = { 278 | 'music_list': self.music_list, 279 | 'volume': self.volume, 280 | 'music_url': self.music_url, 281 | 'music_now': self.music_now, 282 | 'music_dir': self.music_dir, 283 | 'music_info': self.music_info.to_dict() if self.music_info else None 284 | } 285 | return json.dumps(data) 286 | 287 | def load_config_file(self, filename="config.json"): 288 | """ 289 | 加载json格式文件,读入到程序里 290 | """ 291 | if os.path.exists(filename): 292 | with open(filename, 'r') as file: 293 | config_data = file.read() 294 | self.import_config_from_json(config_data) 295 | 296 | # ----------------调试代码-------------------- 297 | def display_all_info(self): 298 | print("音乐列表:", self.music_list) 299 | print("音量:", self.volume) 300 | print("当前音乐地址:", self.music_url) 301 | print("当前音乐时间截点:", self.music_now) 302 | print("当前播放目录:", self.music_dir) 303 | if self.music_info: 304 | pass 305 | # self.music_info.display_metadata() 306 | -------------------------------------------------------------------------------- /form.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | from typing import List 3 | 4 | from PyQt5 import QtWidgets, QtGui, QtCore 5 | from PyQt5.QtGui import QImage, QPixmap 6 | from audio import Config 7 | 8 | 9 | class Ui_Form(QtWidgets.QWidget): 10 | """ 11 | 播放列表的ui 12 | """ 13 | def __init__(self, Main_Window: object, music_list: List[Config] = None): 14 | 15 | super().__init__() 16 | 17 | self.Main_Window = Main_Window 18 | 19 | self.music_list = music_list 20 | 21 | self.setWindowTitle("播放列表") 22 | self.setStyleSheet("background:rgb(27, 27, 27)") 23 | 24 | self.setMinimumSize(QtCore.QSize(480, 150)) 25 | 26 | self.icon1 = QtGui.QIcon() 27 | self.icon1.addPixmap(QtGui.QPixmap("resources/list.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 28 | self.setWindowIcon(self.icon1) 29 | 30 | # 创建垂直布局 31 | self.vertical_layout = QtWidgets.QVBoxLayout(self) 32 | 33 | # 创建滚动区域 34 | self.scroll_area = QtWidgets.QScrollArea(self) 35 | self.scroll_area.setWidgetResizable(True) 36 | 37 | # 创建列表窗口 38 | self.list_widget = QtWidgets.QWidget(self.scroll_area) 39 | self.list_layout = QtWidgets.QVBoxLayout(self.list_widget) 40 | 41 | if music_list is not None: 42 | for bean in music_list: 43 | # 读取播放列表里每一个播放配置单元,并赋值给每一个音乐选择按钮button/QPushButton,装进滚动容器scroll_area/QScrollArea 44 | 45 | button = QtWidgets.QPushButton(self.list_widget) 46 | 47 | button.setMinimumSize(QtCore.QSize(380, 80)) 48 | button.setMaximumSize(QtCore.QSize(9990, 80)) 49 | 50 | button.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 51 | 52 | # 需要判定音乐标题是否为可读模式 53 | if bean.music_info.title: 54 | button.setText(bean.music_info.title) 55 | else: 56 | button.setText("暂无音乐 - 空白") 57 | 58 | # 判定音乐文件封面,装载进音乐选择按钮作为图标 59 | icon = QtGui.QIcon() 60 | if bean.music_info.album_cover: 61 | icon.addPixmap(QtGui.QPixmap(bit_to_map(bean.music_info.album_cover)), QtGui.QIcon.Normal, 62 | QtGui.QIcon.Off) 63 | else: 64 | icon.addPixmap(QtGui.QPixmap("resources/default.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 65 | 66 | button.setIcon(icon) 67 | button.setIconSize(QtCore.QSize(90, 90)) 68 | 69 | bean.music_dir = self.Main_Window.get_dir() 70 | button.clicked.connect(partial(self.chooce, value=bean)) # 连接按钮点击事件 71 | 72 | button.setStyleSheet("background:rgb(37, 37, 37);" 73 | "text-align: left; " 74 | "padding-left: -2px;" 75 | "color: white; /* 文本颜色,这里是红色 */" 76 | "font-family: Microsoft YaHei, sans-serif; /* 字体族 */" 77 | "font-size: 28px; /* 字体大小 */" 78 | "font-weight: bold; /* 字体粗细 */") 79 | self.list_layout.addWidget(button) 80 | 81 | else: 82 | # 音乐列表为空,则加载默认播放选择按钮 83 | button = QtWidgets.QPushButton(self.list_widget) 84 | 85 | button.setMinimumSize(QtCore.QSize(380, 80)) 86 | button.setMaximumSize(QtCore.QSize(9990, 80)) 87 | 88 | button.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 89 | button.setText("暂无音乐 - 空白") 90 | 91 | icon = QtGui.QIcon() 92 | icon.addPixmap(QtGui.QPixmap("resources/default.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 93 | button.setIcon(icon) 94 | button.setIconSize(QtCore.QSize(90, 90)) 95 | 96 | button.clicked.connect(self.show_void) # 连接按钮点击事件 97 | 98 | button.setStyleSheet("background:rgb(37, 37, 37);" 99 | "text-align: left; " 100 | "padding-left: -2px;" 101 | "color: white; /* 文本颜色,这里是红色 */" 102 | "font-family: Microsoft YaHei, sans-serif; /* 字体族 */" 103 | "font-size: 28px; /* 字体大小 */" 104 | "font-weight: bold; /* 字体粗细 */") 105 | self.list_layout.addWidget(button) 106 | 107 | self.scroll_area.setWidget(self.list_widget) 108 | self.vertical_layout.addWidget(self.scroll_area) 109 | 110 | def show_void(self): 111 | # 在这里添加显示图片和文字的逻辑,可以使用QMessageBox、QDialog等 112 | # 可以考虑创建一个新的窗口来显示详细信息 113 | 114 | # 以一个警告窗QMessageBox/QMessageBox() 作为空白列表提示窗口 115 | QMessageBox = QtWidgets.QMessageBox() 116 | QMessageBox.setWindowIcon(self.icon1) 117 | QMessageBox.setWindowTitle("提示") 118 | QMessageBox.setInformativeText("请返回主菜单\n选择音乐文件目录~ Tv T") 119 | QMessageBox.exec_() 120 | 121 | def chooce(self, value: Config): 122 | """ 123 | 选择一个音乐进行播放,并从00:00时刻开始播放 124 | """ 125 | if value: 126 | if value.music_url is not self.Main_Window.config.music_url: 127 | self.Main_Window.music_stop() 128 | self.Main_Window.set_config(value) 129 | self.Main_Window.bean.start_time = 0 130 | self.Main_Window.music_play() 131 | 132 | 133 | def bit_to_map(binary_data): 134 | image = QImage.fromData(binary_data) 135 | pixmap = QPixmap.fromImage(image) 136 | return pixmap 137 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Form implementation generated from reading ui file 'main.ui' 3 | # 4 | # Created by: PyQt5 UI code generator 5.15.9 5 | # 6 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 7 | # run again. Do not edit this file unless you know what you are doing. 8 | import logging 9 | import os 10 | import random 11 | from functools import partial 12 | 13 | from PyQt5 import QtCore, QtGui, QtWidgets 14 | import numba 15 | from PyQt5.QtCore import QTimer, QPropertyAnimation 16 | from PyQt5.QtGui import QImage, QPixmap 17 | from PyQt5.QtWidgets import QGridLayout 18 | 19 | from audio import PlayBean, MusicReader, Config 20 | from form import Ui_Form 21 | 22 | # 设置日志配置 23 | logging.basicConfig(filename='error.log', level=logging.ERROR) 24 | 25 | 26 | class Ui_Main(object): 27 | def __init__(self): 28 | 29 | # A 顺序播放; B 随机播放 30 | self.switch_status = "A" 31 | 32 | # 全局音量值的备份变量 33 | self.volume_temp = 30 34 | 35 | # 播放列表单元配置类config的列表容器 36 | self.music_list = [] 37 | 38 | # 播放列表指定的目录地址字符串 39 | self.music_dir = "" 40 | 41 | # 播放列表的ui初始化 42 | self.ui_form = Ui_Form(Main_Window=self) 43 | 44 | # 初始化全局单元配置类 45 | self.config = Config() 46 | self.config.load_config_file() 47 | 48 | # 初始化音乐总长,播放器实体,以及播放清单 49 | self.music_length = self.config.music_info.music_length / 1000 50 | self.bean = PlayBean(volume=self.config.volume, start_time=self.config.music_now) # 初始化播放模块 51 | self.update_list() 52 | 53 | def setupUi(self, Main): 54 | Main.setObjectName("Main") 55 | Main.resize(800, 500) 56 | Main.setMinimumSize(QtCore.QSize(800, 500)) 57 | Main.setMaximumSize(QtCore.QSize(16000000, 16777215)) 58 | font = QtGui.QFont() 59 | font.setFamily("黑体") 60 | font.setPointSize(10) 61 | font.setBold(False) 62 | font.setWeight(50) 63 | 64 | icon_main = QtGui.QIcon() 65 | icon_main.addPixmap(QtGui.QPixmap("resources/head.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 66 | Main.setWindowIcon(icon_main) 67 | Main.setIconSize(QtCore.QSize(50, 50)) 68 | Main.setFont(font) 69 | 70 | Main.setWindowTitle("音乐播放器") 71 | Main.setAutoFillBackground(False) 72 | Main.setStyleSheet("background:rgb(20, 20, 20)") 73 | 74 | self.central = QtWidgets.QWidget(Main) 75 | self.central.setObjectName("central") 76 | 77 | # 封面图像QLabel 78 | self.diskPlay = QtWidgets.QLabel(self.central) 79 | self.diskPlay.setGeometry(QtCore.QRect(40, 20, 321, 291)) 80 | self.diskPlay.setStyleSheet("QLabel {\n" 81 | " background-color: #F0F0F0;\" # 设置背景颜色\n" 82 | " color: #000000;\" # 设置文本颜色\n" 83 | " border: 2px solid #FFFFFF;\" # 设置边框,白色\n" 84 | " border-radius: 10px;\" # 设置圆角半径\n" 85 | "}") 86 | self.diskPlay.setText("") 87 | self.diskPlay.setTextFormat(QtCore.Qt.AutoText) 88 | self.diskPlay.setPixmap(QtGui.QPixmap("resources/default.png")) 89 | 90 | self.diskPlay.setScaledContents(True) 91 | self.diskPlay.setObjectName("diskPlay") 92 | 93 | # 歌名 QLabel 94 | self.songName = QtWidgets.QLabel(self.central) 95 | self.songName.setGeometry(QtCore.QRect(400, 30, 350, 30)) 96 | self.songName.setMinimumSize(QtCore.QSize(350, 35)) 97 | self.songName.setMaximumSize(QtCore.QSize(9900, 35)) 98 | self.songName.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor)) 99 | self.songName.setLayoutDirection(QtCore.Qt.LeftToRight) 100 | self.songName.setStyleSheet("font: 75 15pt \"微软雅黑\";\n" 101 | "color:white;\n" 102 | "") 103 | self.songName.setObjectName("songName") 104 | self.songName.setText("暂无媒体 - 空白" + " ") 105 | 106 | # 歌曲信息 QLabel 107 | self.songInfo = QtWidgets.QLabel(self.central) 108 | self.songInfo.setGeometry(QtCore.QRect(400, 70, 350, 231)) 109 | self.songInfo.setMinimumSize(QtCore.QSize(350, 231)) 110 | self.songInfo.setMaximumSize(QtCore.QSize(9900, 231)) 111 | self.songInfo.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor)) 112 | self.songInfo.setLayoutDirection(QtCore.Qt.LeftToRight) 113 | self.songInfo.setStyleSheet("font: 75 10pt \"微软雅黑\";\n" 114 | "color:white;\n" 115 | "") 116 | self.songInfo.setObjectName("songInfo") 117 | 118 | # 目录选择器 QPushButton 119 | self.file = QtWidgets.QPushButton(self.central) 120 | self.file.setGeometry(QtCore.QRect(750, 0, 45, 45)) 121 | self.file.setMinimumSize(QtCore.QSize(45, 45)) 122 | self.file.setMaximumSize(QtCore.QSize(45, 45)) 123 | self.file.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 124 | self.file.setText("") 125 | icon = QtGui.QIcon() 126 | icon.addPixmap(QtGui.QPixmap("resources/file.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 127 | self.file.setIcon(icon) 128 | self.file.setIconSize(QtCore.QSize(30, 30)) 129 | self.file.setObjectName("file") 130 | 131 | self.layoutWidget = QtWidgets.QWidget(self.central) 132 | self.layoutWidget.setGeometry(QtCore.QRect(50, 330, 707, 122)) 133 | self.layoutWidget.setObjectName("layoutWidget") 134 | self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget) 135 | self.verticalLayout.setContentsMargins(0, 0, 0, 0) 136 | self.verticalLayout.setObjectName("verticalLayout") 137 | 138 | self.runDisplay = QtWidgets.QHBoxLayout() 139 | self.runDisplay.setObjectName("runDisplay") 140 | 141 | # 左时间:进度时间,实时更新,QLabel 142 | self.timeNow = QtWidgets.QLabel(self.layoutWidget) 143 | self.timeNow.setMinimumSize(QtCore.QSize(60, 15)) 144 | self.timeNow.setMaximumSize(QtCore.QSize(60, 15)) 145 | self.timeNow.setStyleSheet("\n" 146 | "font: 75 9pt \"微软雅黑\";\n" 147 | "color:white;\n" 148 | "") 149 | self.timeNow.setObjectName("timeNow") 150 | self.timeNow.setText(format_time(self.config.music_now + 1)) 151 | 152 | self.runDisplay.addWidget(self.timeNow) 153 | 154 | # 进度条,实时更新,QSlider 155 | self.runningBar = QtWidgets.QSlider(self.layoutWidget) 156 | self.runningBar.setMinimumSize(QtCore.QSize(320, 20)) 157 | self.runningBar.setMaximumSize(QtCore.QSize(16777215, 20)) 158 | self.runningBar.setStyleSheet("QSlider::groove:horizontal {\n" 159 | " border: 0px solid rgb(254, 255, 253);\n" 160 | " height: 10px;\n" 161 | " background:rgb(255, 152, 120)rgb(97, 97, 97);\n" 162 | " \n" 163 | "}\n" 164 | "\n" 165 | "QSlider::handle:horizontal {\n" 166 | " background:rgb(255, 82, 52);\n" 167 | " color: rgb(116, 50, 24);\n" 168 | " height: 8px;\n" 169 | " width: 18px;\n" 170 | "}") 171 | self.runningBar.setOrientation(QtCore.Qt.Horizontal) 172 | self.runningBar.setObjectName("runningBar") 173 | self.runningBar.setValue(int(self.config.music_now * 100 / self.music_length)) 174 | 175 | self.runDisplay.addWidget(self.runningBar) 176 | 177 | # 右时间:总时长,切换更新 QLabel 178 | self.timeAll = QtWidgets.QLabel(self.layoutWidget) 179 | self.timeAll.setMinimumSize(QtCore.QSize(60, 15)) 180 | self.timeAll.setMaximumSize(QtCore.QSize(60, 15)) 181 | self.timeAll.setStyleSheet("\n" 182 | "font: 75 9pt \"微软雅黑\";\n" 183 | "color:white;\n" 184 | "") 185 | self.timeAll.setObjectName("timeAll") 186 | 187 | self.runDisplay.addWidget(self.timeAll) 188 | self.verticalLayout.addLayout(self.runDisplay) 189 | 190 | self.playDisplay = QtWidgets.QHBoxLayout() 191 | self.playDisplay.setObjectName("playDisplay") 192 | 193 | # 上一首, QPushButton 194 | self.left = QtWidgets.QPushButton(self.layoutWidget) 195 | self.left.setMinimumSize(QtCore.QSize(45, 45)) 196 | self.left.setMaximumSize(QtCore.QSize(45, 45)) 197 | self.left.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 198 | self.left.setText("") 199 | icon1 = QtGui.QIcon() 200 | icon1.addPixmap(QtGui.QPixmap("resources/left.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 201 | self.left.setIcon(icon1) 202 | self.left.setIconSize(QtCore.QSize(45, 45)) 203 | self.left.setObjectName("left") 204 | 205 | self.playDisplay.addWidget(self.left) 206 | 207 | # 播放按钮 QPushuButton 208 | self.play = QtWidgets.QPushButton(self.layoutWidget) 209 | self.play.setMinimumSize(QtCore.QSize(90, 90)) 210 | self.play.setMaximumSize(QtCore.QSize(90, 90)) 211 | self.play.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 212 | self.play.setText("") 213 | icon2 = QtGui.QIcon() 214 | icon2.addPixmap(QtGui.QPixmap("resources/play.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 215 | self.play.setIcon(icon2) 216 | self.play.setIconSize(QtCore.QSize(75, 75)) 217 | self.play.setCheckable(False) 218 | self.play.setAutoRepeat(False) 219 | self.play.setAutoDefault(False) 220 | self.play.setObjectName("play") 221 | 222 | self.playDisplay.addWidget(self.play) 223 | 224 | # 下一首 QPushButton 225 | self.right = QtWidgets.QPushButton(self.layoutWidget) 226 | self.right.setMinimumSize(QtCore.QSize(45, 45)) 227 | self.right.setMaximumSize(QtCore.QSize(45, 45)) 228 | self.right.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 229 | self.right.setText("") 230 | icon3 = QtGui.QIcon() 231 | icon3.addPixmap(QtGui.QPixmap("resources/right.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 232 | self.right.setIcon(icon3) 233 | self.right.setIconSize(QtCore.QSize(30, 30)) 234 | self.right.setAutoExclusive(False) 235 | self.right.setObjectName("right") 236 | 237 | self.playDisplay.addWidget(self.right) 238 | 239 | # 播放清单ui跳转, QPushButton 240 | self.list = QtWidgets.QPushButton(self.layoutWidget) 241 | self.list.setMinimumSize(QtCore.QSize(45, 45)) 242 | self.list.setMaximumSize(QtCore.QSize(45, 45)) 243 | self.list.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 244 | self.list.setText("") 245 | icon4 = QtGui.QIcon() 246 | icon4.addPixmap(QtGui.QPixmap("resources/list.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 247 | self.list.setIcon(icon4) 248 | self.list.setIconSize(QtCore.QSize(30, 30)) 249 | self.list.setObjectName("list") 250 | 251 | # 音量键切换,QPushButton 252 | self.sound = QtWidgets.QPushButton(self.central) 253 | self.sound.setGeometry(QtCore.QRect(765, 292, 30, 30)) 254 | self.sound.setMinimumSize(QtCore.QSize(30, 30)) 255 | self.sound.setMaximumSize(QtCore.QSize(45, 45)) 256 | self.sound.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 257 | self.sound.setText("") 258 | 259 | icon5 = QtGui.QIcon() 260 | icon5.addPixmap(QtGui.QPixmap("resources/on.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 261 | self.sound.setIcon(icon5) 262 | self.sound.setIconSize(QtCore.QSize(20, 20)) 263 | 264 | self.sound.setObjectName("sound") 265 | 266 | self.playDisplay.addWidget(self.list) 267 | 268 | # 播放模式选择器 QComboBox 269 | self.comboBox = QtWidgets.QComboBox(self.layoutWidget) 270 | self.comboBox.setMinimumSize(QtCore.QSize(130, 35)) 271 | self.comboBox.setMaximumSize(QtCore.QSize(130, 35)) 272 | self.comboBox.setObjectName("comboBox") 273 | 274 | # 添加两个项 275 | self.comboBox.addItem("顺序播放") 276 | self.comboBox.addItem("随机播放") 277 | self.comboBox.addItem("单曲循环") 278 | 279 | self.comboBox.setStyleSheet("QComboBox {" 280 | " color: white;" # 文字颜色 281 | " font: 75 10pt \"微软雅黑\";" 282 | "}") 283 | 284 | self.playDisplay.addWidget(self.comboBox) 285 | self.verticalLayout.addLayout(self.playDisplay) 286 | 287 | # 音量条 QSlider 288 | self.audioBar = QtWidgets.QSlider(self.central) 289 | self.audioBar.setGeometry(QtCore.QRect(770, 330, 20, 121)) 290 | self.audioBar.setMinimumSize(QtCore.QSize(20, 121)) 291 | self.audioBar.setMaximumSize(QtCore.QSize(20, 121)) 292 | self.audioBar.setStyleSheet("QSlider::groove:vertical {\n" 293 | " border: 0px solid rgb(254, 255, 253);\n" 294 | "width: 10px;\n" 295 | " height: 120px;\n" 296 | " background:rgb(255, 152, 120)rgb(97, 97, 97);\n" 297 | " \n" 298 | "}\n" 299 | "\n" 300 | "QSlider::handle:vertical {\n" 301 | " background:rgb(255, 82, 52);\n" 302 | " color: rgb(116, 50, 24);\n" 303 | " height: 10px;\n" 304 | " width: 10px;\n" 305 | "}") 306 | self.audioBar.setOrientation(QtCore.Qt.Vertical) 307 | self.audioBar.setObjectName("audioBar") 308 | self.audioBar.setValue(int(self.config.volume)) 309 | 310 | Main.setCentralWidget(self.central) 311 | self.statusbar = QtWidgets.QStatusBar(Main) 312 | self.statusbar.setObjectName("statusbar") 313 | Main.setStatusBar(self.statusbar) 314 | 315 | self.retranslateUi(Main) 316 | self.play.clicked.connect(self.play.update) # type: ignore 317 | QtCore.QMetaObject.connectSlotsByName(Main) 318 | 319 | self.songName_timer = QTimer(self) 320 | self.songName_timer.start(900) # 设置定时器间隔,单位为毫秒 321 | 322 | self.running_timer = QTimer(self) 323 | self.running_timer.start(100) # 设置定时器间隔,单位为毫秒 324 | 325 | self.play.clicked.connect(self.play_slot) 326 | 327 | self.runningBar.valueChanged.connect(self.update_music_now) 328 | self.runningBar.sliderReleased.connect(self.music_play) 329 | 330 | self.audioBar.valueChanged.connect(self.update_volume_now) 331 | 332 | self.sound.clicked.connect(self.switch_sound) 333 | 334 | self.songName_timer.timeout.connect(self.scroll_title) 335 | self.running_timer.timeout.connect(self.update_running) 336 | 337 | self.list.clicked.connect(self.form_show) 338 | self.file.clicked.connect(self.show_directory_dialog) 339 | 340 | self.right.clicked.connect(partial(self.switch_music, value=1)) 341 | self.left.clicked.connect(partial(self.switch_music, value=-1)) 342 | 343 | self.comboBox.currentIndexChanged.connect(self.combo_change) 344 | 345 | self.update_ui() 346 | # 初始化第一首音乐的ui 347 | 348 | def play_slot(self): 349 | # 暂停 350 | if self.bean.is_busy() is True: 351 | self.music_pause() 352 | # 播放 353 | else: 354 | self.music_play() 355 | 356 | def music_play(self): 357 | """ 358 | 播放音乐的方法。 359 | 如果配置了音乐 URL,将更新用户界面,播放音乐,并启动歌曲名称定时器。 360 | """ 361 | 362 | if self.config.music_url: 363 | self.update_ui() 364 | self.bean.play_music(file_path=self.config.music_url, 365 | start_time=self.bean.start_time, 366 | volume=self.config.volume) 367 | 368 | self.songName_timer.start() 369 | icon2 = QtGui.QIcon() 370 | icon2.addPixmap(QtGui.QPixmap("resources/pause.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 371 | self.play.setIcon(icon2) 372 | self.play.setIconSize(QtCore.QSize(75, 75)) 373 | 374 | def music_pause(self): 375 | """ 376 | 暂停音乐的方法。 377 | 如果音乐正在播放,则暂停音乐,并停止歌曲名称定时器。 378 | """ 379 | if self.bean.is_busy(): 380 | self.bean.pause_music() 381 | 382 | self.songName_timer.stop() 383 | icon2 = QtGui.QIcon() 384 | icon2.addPixmap(QtGui.QPixmap("resources/play.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 385 | self.play.setIcon(icon2) 386 | self.play.setIconSize(QtCore.QSize(75, 75)) 387 | 388 | def music_stop(self): 389 | """ 390 | 停止音乐的方法。 391 | 如果音乐正在播放,则停止音乐,保存配置,并停止歌曲名称定时器。 392 | """ 393 | if self.bean.is_busy(): 394 | self.bean.stop_music() 395 | 396 | self.config.music_now = 0 397 | self.songName_timer.stop() 398 | 399 | icon2 = QtGui.QIcon() 400 | icon2.addPixmap(QtGui.QPixmap("resources/play.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 401 | self.play.setIcon(icon2) 402 | self.play.setIconSize(QtCore.QSize(75, 75)) 403 | 404 | def update_music_now(self, value): 405 | """ 406 | 根据进度条值计算更新音乐播放位置 407 | :param value 进度条传递过来的值 408 | """ 409 | self.config.music_now = value / 100 * self.music_length 410 | self.timeNow.setText(format_time(self.config.music_now + 1)) 411 | self.bean.set_start(self.config.music_now) 412 | 413 | def update_volume_now(self, value): 414 | """ 415 | 根据进度条值计算更新音乐播放位置 416 | :param value: 音量条传递过来的值 417 | """ 418 | self.config.volume = value 419 | self.bean.set_volume(value) 420 | self.change_sound_icon() 421 | 422 | def update_ui(self): 423 | """ 424 | 页面更新器,更新该页面的封面、标题、音乐简介、和音乐总时长 425 | """ 426 | 427 | # 更新封面 428 | if self.config.music_info.album_cover is None: 429 | self.diskPlay.setPixmap(QtGui.QPixmap("resources/default.png")) 430 | else: 431 | self.diskPlay.setPixmap(bit_to_map(self.config.music_info.album_cover)) 432 | 433 | # 更新标题 434 | if self.config.music_info.title is None: 435 | self.songName.setText("暂无媒体 - 空白" + " ") 436 | else: 437 | self.songName.setText(self.config.music_info.title + " ") 438 | 439 | # 更新总时间 440 | self.music_length = self.config.music_info.music_length / 1000 441 | self.timeAll.setText(format_time(self.music_length)) 442 | 443 | # 更新简介 444 | self.songInfo.setText(self.config.music_info.get_info_str()) 445 | 446 | def switch_sound(self): 447 | """ 448 | 音量更新器,实时更新当前播放文件的音量大小,并根据情况修改静音时的音量键图标 449 | """ 450 | 451 | # 有声 452 | if self.config.volume - 0 > 0.0001: 453 | self.volume_temp = self.config.volume 454 | self.config.volume = 0 455 | self.bean.set_volume(self.config.volume) 456 | self.change_sound_icon() 457 | self.audioBar.setValue(0) 458 | else: 459 | self.config.volume = self.volume_temp 460 | self.bean.set_volume(self.config.volume) 461 | self.change_sound_icon() 462 | self.audioBar.setValue(int(self.config.volume)) 463 | 464 | def change_sound_icon(self): 465 | """ 466 | 静音切换器,根据音量键按下切换静音、非静音模式,静音前会备份全局音量值 467 | """ 468 | 469 | # 有声 470 | if self.config.volume - 0 > 0.0001: 471 | icon5 = QtGui.QIcon() 472 | icon5.addPixmap(QtGui.QPixmap("resources/on.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 473 | self.sound.setIcon(icon5) 474 | self.sound.setIconSize(QtCore.QSize(20, 20)) 475 | 476 | # 无声 477 | else: 478 | icon5 = QtGui.QIcon() 479 | icon5.addPixmap(QtGui.QPixmap("resources/off.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 480 | self.sound.setIcon(icon5) 481 | self.sound.setIconSize(QtCore.QSize(20, 20)) 482 | 483 | def scroll_title(self): 484 | """ 485 | 标题滚动器,根据计时器实时修改标题文字排版,达成假滚动动态效果 486 | """ 487 | current_text = self.songName.text() 488 | scrolled_text = current_text[1:] + current_text[0] 489 | self.songName.setText(scrolled_text) 490 | 491 | def update_running(self): 492 | """ 493 | running更新器,实时更新当前显示时间和进度条位置,并在结束时切换音乐 494 | """ 495 | self.config.music_now = self.bean.start_time 496 | self.timeNow.setText(format_time(self.config.music_now + 1)) 497 | self.runningBar.setValue(int(self.config.music_now * 100 / self.music_length)) 498 | 499 | if abs(self.music_length - self.config.music_now) <= 0.48: 500 | self.music_stop() 501 | self.switch_music() 502 | 503 | def switch_music(self, value: int = 1): 504 | """ 505 | 音乐切换器,根据combobox选择情况调用相应参数进行切换 506 | """ 507 | 508 | if self.music_list and self.config: 509 | self.music_stop() 510 | r_num = value 511 | if self.switch_status == "B": 512 | # 禁止出现0随机 513 | non_zero_range = (list(range(-len(self.music_list), 0)) 514 | + list(range(1, len(self.music_list) + 1))) 515 | r_num = random.choice(non_zero_range) 516 | elif self.switch_status == "C": 517 | r_num = 0 518 | 519 | self.play_next(value=r_num) 520 | self.update_ui() 521 | self.music_play() 522 | 523 | def play_next(self, value: int = 1): 524 | """ 525 | 音乐切换函数,根据value值或者随机值按照环形遍历原则切换 526 | """ 527 | if self.music_list and self.config: 528 | self.bean.start_time = 0 529 | index = 0 530 | for i, element in enumerate(self.music_list): 531 | if element.music_url == self.config.music_url: 532 | index = i 533 | break 534 | # 计算环形遍历后的索引 535 | total = len(self.music_list) 536 | next_index = (index + value) % total 537 | self.set_config(self.music_list[next_index]) 538 | 539 | def form_show(self): 540 | """ 541 | 打开播放清单的ui 542 | """ 543 | if not self.music_list: 544 | self.ui_form.show() 545 | else: 546 | self.ui_form = Ui_Form(Main_Window=self, music_list=self.music_list) 547 | self.ui_form.show() 548 | 549 | def show_directory_dialog(self): 550 | """ 551 | 打开目录选择器 552 | """ 553 | options = QtWidgets.QFileDialog.Options() 554 | options |= QtWidgets.QFileDialog.ShowDirsOnly # 只显示目录 555 | self.music_dir = QtWidgets.QFileDialog.getExistingDirectory(self, "选择播放目录", "", options=options) 556 | if self.music_dir: 557 | self.config.music_dir = self.music_dir 558 | self.update_list() 559 | self.update_ui() 560 | 561 | def update_list(self): 562 | """ 563 | 更新播放清单数据 564 | """ 565 | if self.config.music_dir: 566 | self.scan_music(self.config.music_dir) 567 | self.music_dir = self.config.music_dir 568 | if not self.music_list: 569 | self.config = self.music_list[0] 570 | 571 | # TODO 572 | else: 573 | logging.error(f"该目录找不到音乐文件: {self.config.music_dir} ") 574 | 575 | def scan_music(self, directory): 576 | """ 577 | 扫描指定目录下的所有音乐文件,转为单元配置类存储进全局容器 578 | """ 579 | music_formats = ['.mp3'] # 主流音乐格式列表,可以根据实际需要扩展 能力有限暂时只支持mp3格式 580 | try: 581 | # 重置清单,以防止混杂交错 582 | self.music_list = [] 583 | 584 | for root, dirs, files in os.walk(directory): 585 | for file in files: 586 | if any(file.lower().endswith(format) for format in music_formats): 587 | 588 | # 规范化地址,防止左右斜杠符混用 589 | url_temp = os.path.normpath(os.path.join(root, file)) 590 | 591 | # 检查是否已经存在相同的 music_url,若存在则不加入 592 | if url_temp not in [item.music_url for item in self.music_list]: 593 | conf_temp = Config(music_url=url_temp) 594 | self.music_list.append(conf_temp) 595 | 596 | except Exception as e: 597 | logging.error(f"扫描音乐文件时发生错误: {e}") 598 | 599 | def combo_change(self, index): 600 | """ 601 | 根据ComboBox选择的项设置全局变量 self.switch_status 602 | """ 603 | if index == 0: 604 | self.switch_status = "A" 605 | elif index == 1: 606 | self.switch_status = "B" 607 | elif index == 2: 608 | self.switch_status = "C" 609 | 610 | def closeEvent(self, event): 611 | """ 612 | 主窗口关闭事件 613 | :param event: 614 | """ 615 | self.config.music_dir = self.music_dir 616 | self.config.save_config_file() 617 | 618 | if self.bean.is_busy(): 619 | self.bean.stop_music() 620 | if self.ui_form.isVisible(): 621 | self.ui_form.close() 622 | event.accept() 623 | 624 | # -------------决定需要废弃的函数(暂不能删除)------------- 625 | def retranslateUi(self, Main): 626 | """ 627 | 再翻译功能 628 | """ 629 | _translate = QtCore.QCoreApplication.translate 630 | self.songName.setText(_translate("Main", "暂无媒体 - 空白")) 631 | self.songInfo.setText(_translate("Main", "<空>")) 632 | self.timeNow.setText(_translate("Main", "00:00")) 633 | self.timeAll.setText(_translate("Main", "00:00")) 634 | 635 | def set_config(self, config: Config): 636 | """ 获取全局单元配置 """ 637 | self.config = config 638 | 639 | def get_dir(self): 640 | """ 获取全局播放清单 """ 641 | return self.music_dir 642 | 643 | 644 | # -------------外部工具------------- 645 | def bit_to_map(binary_data): 646 | """ 二进制字节流变为图像格式 """ 647 | image = QImage.fromData(binary_data) 648 | pixmap = QPixmap.fromImage(image) 649 | return pixmap 650 | 651 | 652 | def format_time(seconds): 653 | """ 秒数变为00:00 格式字符串 """ 654 | minutes, seconds = divmod(seconds - 1, 60) 655 | return f"{int(minutes):02d}:{int(seconds):02d}" 656 | -------------------------------------------------------------------------------- /resources/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aki-zone/PyQt5-Music-Player/ecaea89677823a5405c70e5eb176d4dd447c0d22/resources/default.png -------------------------------------------------------------------------------- /resources/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aki-zone/PyQt5-Music-Player/ecaea89677823a5405c70e5eb176d4dd447c0d22/resources/file.png -------------------------------------------------------------------------------- /resources/head.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aki-zone/PyQt5-Music-Player/ecaea89677823a5405c70e5eb176d4dd447c0d22/resources/head.png -------------------------------------------------------------------------------- /resources/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aki-zone/PyQt5-Music-Player/ecaea89677823a5405c70e5eb176d4dd447c0d22/resources/left.png -------------------------------------------------------------------------------- /resources/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aki-zone/PyQt5-Music-Player/ecaea89677823a5405c70e5eb176d4dd447c0d22/resources/list.png -------------------------------------------------------------------------------- /resources/off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aki-zone/PyQt5-Music-Player/ecaea89677823a5405c70e5eb176d4dd447c0d22/resources/off.png -------------------------------------------------------------------------------- /resources/on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aki-zone/PyQt5-Music-Player/ecaea89677823a5405c70e5eb176d4dd447c0d22/resources/on.png -------------------------------------------------------------------------------- /resources/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aki-zone/PyQt5-Music-Player/ecaea89677823a5405c70e5eb176d4dd447c0d22/resources/pause.png -------------------------------------------------------------------------------- /resources/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aki-zone/PyQt5-Music-Player/ecaea89677823a5405c70e5eb176d4dd447c0d22/resources/play.png -------------------------------------------------------------------------------- /resources/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aki-zone/PyQt5-Music-Player/ecaea89677823a5405c70e5eb176d4dd447c0d22/resources/right.png -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from PyQt5.QtWidgets import QApplication, QMainWindow 4 | import sys 5 | 6 | 7 | from main import Ui_Main 8 | 9 | 10 | 11 | 12 | class MainApplication(QMainWindow, Ui_Main): 13 | def __init__(self): 14 | super(MainApplication, self).__init__() 15 | self.setupUi(self) 16 | 17 | 18 | def run_application(): 19 | app = QApplication(sys.argv) 20 | main_app = MainApplication() 21 | main_app.show() 22 | 23 | sys.exit(app.exec_()) 24 | 25 | 26 | if __name__ == "__main__": 27 | run_application() 28 | --------------------------------------------------------------------------------