当前位置: 首页 > article >正文

本地音乐播放器(有UI界面)

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()


http://www.kler.cn/a/549766.html

相关文章:

  • Python VsCode DeepSeek接入
  • 从零开始学Python爬虫:(二)使用基本库urllib(下)
  • React 中的状态和属性有什么区别?
  • 在 Go 中实现事件溯源:构建高效且可扩展的系统
  • iOS事件传递和响应
  • springboot245-springboot项目评审系统(源码+论文+PPT+部署讲解等)
  • word文档提取信息
  • 从安装软件到flask框架搭建可视化大屏(二)——创建一个flask页面,搭建可视化大屏,零基础也可以学会
  • 鸿蒙NEXT开发-自定义构建函数
  • mac docker镜像加速正确配置方式
  • rabbitmq五种模式的总结——附java-se实现(详细)
  • Vue 自动配置表单 el-switch等不常用组件覆盖默认值问题
  • Versal - 基础5(裸机开发 AIE-ML+Vitis2024.2界面aie report介绍)
  • 基于Python实现的缓存淘汰替换策略算法,该算法将缓存分区
  • 网络安全-攻击流程-应用层
  • Java每日精进·45天挑战·Day17
  • 【第3章:卷积神经网络(CNN)——3.1 CNN的基本结构与工作原理】
  • 大语言模型推理中的显存优化 有哪些
  • 如何利用Vuex的插件来记录和追踪状态变化?
  • Linux下tomcat实现进程守护