
import tkinter as tk
from tkinter import ttk, filedialog
from pygame import mixer
import os
import time
import threading
from mutagen.mp3 import MP3
class MusicPlayer:
def __init__(self, root):
self.root = root
self.root.title("音乐播放器")
self.root.geometry("800x600")
mixer.init()
# 播放控制变量
self.current_song = ""
self.paused = False
self.playlist = []
self.current_index = 0
self.volume = 0.7
self.volume_label = None # 添加初始化
# 创建UI
self.create_widgets()
self.create_menu()
self.update_volume()
def create_widgets(self):
# 设置主题颜色
self.bg_color = "#2E2E2E"
self.fg_color = "#FFFFFF"
self.accent_color = "#1DB954"
self.progress_color = "#535353"
self.root.configure(bg=self.bg_color)
# 顶部控制栏
control_frame = tk.Frame(self.root, bg=self.bg_color)
control_frame.pack(pady=20, fill=tk.X)
# 播放控制按钮(使用符号字体)
btn_style = {'font': ('Segoe UI Symbol', 16), 'bg': self.bg_color,
'fg': self.fg_color, 'bd': 0, 'activebackground': '#404040'}
ttk.Button(control_frame, text="⏮", command=self.prev_song, style='TButton').pack(side=tk.LEFT, padx=10)
self.play_btn = ttk.Button(control_frame, text="▶", command=self.toggle_play, style='TButton')
self.play_btn.pack(side=tk.LEFT, padx=10)
ttk.Button(control_frame, text="⏭", command=self.next_song, style='TButton').pack(side=tk.LEFT, padx=10)
# 在顶部控制栏添加文件操作按钮
file_btn_frame = tk.Frame(self.root, bg=self.bg_color)
file_btn_frame.pack(fill=tk.X, padx=20, pady=10)
# 添加现代化文件操作按钮
ttk.Button(file_btn_frame, text="📁 添加音乐", compound=tk.LEFT,
command=self.add_song, style='TButton').pack(side=tk.LEFT, padx=5)
ttk.Button(file_btn_frame, text="📂 添加文件夹", compound=tk.LEFT,
command=self.add_folder, style='TButton').pack(side=tk.LEFT, padx=5)
# 进度条区域
progress_frame = tk.Frame(self.root, bg=self.bg_color)
progress_frame.pack(fill=tk.X, padx=20, pady=10)
self.time_label = tk.Label(progress_frame, text="00:00", fg=self.fg_color, bg=self.bg_color)
self.time_label.pack(side=tk.LEFT)
self.progress = ttk.Progressbar(progress_frame, orient='horizontal', length=500,
mode='determinate', style="green.Horizontal.TProgressbar")
self.progress.pack(side=tk.LEFT, expand=True, padx=10)
self.duration_label = tk.Label(progress_frame, text="00:00", fg=self.fg_color, bg=self.bg_color)
self.duration_label.pack(side=tk.LEFT)
# 播放列表区域
list_frame = tk.Frame(self.root, bg=self.bg_color)
list_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
# 自定义播放列表样式
style = ttk.Style()
style.configure("Treeview",
background=self.bg_color,
foreground=self.fg_color,
fieldbackground=self.bg_color,
borderwidth=0,
font=('微软雅黑', 10))
style.map('Treeview', background=[('selected', '#404040')])
self.playlist_box = ttk.Treeview(list_frame, columns=('title', 'duration'), show='headings', selectmode='browse')
self.playlist_box.heading('title', text='歌曲标题')
self.playlist_box.heading('duration', text='时长')
self.playlist_box.column('title', width=300)
self.playlist_box.column('duration', width=80, anchor='center')
self.playlist_box.pack(fill=tk.BOTH, expand=True)
# 音量控制面板(完整实现)
volume_frame = tk.Frame(self.root, bg=self.bg_color)
volume_frame.pack(fill=tk.X, padx=20, pady=10)
# 音量图标
self.volume_icon = tk.Label(volume_frame, text="🔊",
fg=self.fg_color, bg=self.bg_color)
self.volume_icon.pack(side=tk.LEFT)
# 音量滑块
style = ttk.Style()
style.configure("Volume.Horizontal.TScale",
troughcolor=self.progress_color,
background=self.accent_color,
thickness=10)
self.volume_scale = ttk.Scale(volume_frame, from_=0, to=1,
command=self._update_volume_callback,
style="Volume.Horizontal.TScale")
self.volume_scale.set(self.volume)
self.volume_scale.pack(side=tk.LEFT, expand=True, padx=10, ipady=3)
# 音量百分比标签
self.volume_label = tk.Label(volume_frame,
text=f"{int(self.volume*100)}%",
fg=self.fg_color, bg=self.bg_color)
self.volume_label.pack(side=tk.LEFT)
# 音量控制按钮
btn_frame = tk.Frame(volume_frame, bg=self.bg_color)
btn_frame.pack(side=tk.LEFT, padx=10)
ttk.Button(btn_frame, text="−", width=2,
command=lambda: self.set_volume(self.volume-0.1)).pack(side=tk.LEFT)
ttk.Button(btn_frame, text="+", width=2,
command=lambda: self.set_volume(self.volume+0.1)).pack(side=tk.LEFT)
# 应用自定义样式
style.configure("TButton", font=('微软雅黑', 10), background=self.accent_color)
style.configure("green.Horizontal.TProgressbar",
troughcolor=self.progress_color,
background=self.accent_color,
thickness=5)
style.map('TButton',
background=[('active', '#1ED760'), ('!active', self.accent_color)],
foreground=[('active', 'white')])
# 绑定键盘事件
self.root.bind('<space>', lambda e: self.toggle_play())
self.root.bind('<Left>', lambda e: self.seek(-5))
self.root.bind('<Right>', lambda e: self.seek(5))
def create_menu(self):
# 创建菜单栏
menubar = tk.Menu(self.root)
# 文件菜单
file_menu = tk.Menu(menubar, tearoff=0)
file_menu.add_command(label="打开文件", command=self.add_song)
file_menu.add_command(label="打开文件夹", command=self.add_folder)
file_menu.add_separator()
file_menu.add_command(label="退出", command=self.root.quit)
menubar.add_cascade(label="文件", menu=file_menu)
self.root.config(menu=menubar)
def add_song(self):
files = filedialog.askopenfilenames(
title="选择音乐文件",
filetypes=(("MP3文件", "*.mp3"), ("所有文件", "*.*"))
)
for f in files:
self.playlist.append(f)
# 获取歌曲时长
audio = MP3(f)
duration = f"{int(audio.info.length // 60):02d}:{int(audio.info.length % 60):02d}"
# 更新插入方式
self.playlist_box.insert('', 'end',
values=(os.path.basename(f), duration))
def add_folder(self):
folder = filedialog.askdirectory()
if folder:
for file in os.listdir(folder):
if file.endswith(".mp3"):
path = os.path.join(folder, file)
self.playlist.append(path)
# 添加时长信息
audio = MP3(path)
duration = f"{int(audio.info.length // 60):02d}:{int(audio.info.length % 60):02d}"
self.playlist_box.insert('', 'end',
values=(file, duration))
def select_song(self, event):
selected = self.playlist_box.selection()
if selected:
# 获取选中行的索引
self.current_index = self.playlist_box.index(selected[0])
self.play_song()
def play_song(self):
if not self.playlist:
return
try:
self.current_song = self.playlist[self.current_index]
mixer.music.load(self.current_song)
mixer.music.play()
# 初始化歌曲长度属性
audio = MP3(self.current_song)
self.song_length = audio.info.length
self.progress["maximum"] = self.song_length
self.update_progress()
self.play_btn.config(text="暂停")
self.paused = False
# 在播放歌曲时更新总时长显示
total_mins, total_secs = divmod(self.song_length, 60)
self.duration_label.config(text=f"{int(total_mins):02d}:{int(total_secs):02d}")
except Exception as e:
print(f"播放错误: {str(e)}")
self.input_text.set("播放错误")
self.expression = ""
def toggle_play(self):
if mixer.music.get_busy() and not self.paused:
mixer.music.pause()
self.play_btn.config(text="播放")
self.paused = True
else:
if self.paused:
mixer.music.unpause()
self.play_btn.config(text="暂停")
self.paused = False
else:
self.play_song()
def next_song(self):
if self.playlist:
self.current_index = (self.current_index + 1) % len(self.playlist)
self.play_song()
def prev_song(self):
if self.playlist:
self.current_index = (self.current_index - 1) % len(self.playlist)
self.play_song()
def _update_volume_callback(self, value):
"""处理滑块事件,避免递归调用"""
if not hasattr(self, '_updating_volume'):
self._updating_volume = True
self.set_volume(float(value))
self._updating_volume = False
def set_volume(self, volume):
# 限制音量范围
volume = max(0.0, min(1.0, volume))
self.volume = volume
# 更新滑块位置(不触发回调)
if not hasattr(self, '_updating_volume'):
self.volume_scale.set(volume)
# 更新标签显示
if self.volume_label:
self.volume_label.config(text=f"{int(volume*100)}%")
# 设置实际音量
mixer.music.set_volume(volume)
def update_volume(self):
self.volume_scale.set(self.volume)
self.root.after(100, self.update_volume)
def update_progress(self):
if mixer.music.get_busy() and not self.paused:
try:
current_time = mixer.music.get_pos() / 1000
self.progress["value"] = current_time
# 添加异常处理
if hasattr(self, 'song_length'):
mins, secs = divmod(current_time, 60)
total_mins, total_secs = divmod(self.song_length, 60)
self.time_label.config(
text=f"{int(mins):02d}:{int(secs):02d} / {int(total_mins):02d}:{int(total_secs):02d}"
)
except Exception as e:
print(f"更新进度错误: {str(e)}")
self.root.after(1000, self.update_progress)
def seek(self, seconds):
if mixer.music.get_busy():
current_pos = mixer.music.get_pos() / 1000
new_pos = max(0, min(current_pos + seconds, self.song_length))
mixer.music.set_pos(new_pos)
if __name__ == "__main__":
root = tk.Tk()
app = MusicPlayer(root)
root.mainloop()