- 519测试
无题
- @ 2026-5-19 17:55:20
等待有缘人。。。。。
2 条评论
-
hzzxhelang LV 4 @ 2026-6-15 17:32:02已修改import os import tkinter as tk from tkinter import ttk, filedialog, messagebox import pygame from mutagen.mp3 import MP3 from mutagen.flac import FLAC from mutagen.oggvorbis import OggVorbis from mutagen.wavpack import WavPack import random import time import re class MusicPlayer: def __init__(self, root): self.root = root self.root.title("稳定版音乐播放器(支持LRC歌词)") self.root.geometry("950x600") # 加宽窗口以容纳歌词 self.root.resizable(False, False) pygame.mixer.init() # 音频列表与索引 self.music_list = [] self.current_index = -1 self.play_mode = "order" self.is_playing = False self.music_length = 0 self.current_file = "" # 歌词相关 self.lyrics = [] # [(时间秒, 文本行), ...] self.current_lyric_index = -1 # 进度控制核心变量 self.dragging = False self.drag_pos = 0.0 self.play_start_time = 0 self.pause_time = 0 self.suffixs = (".mp3", ".flac", ".wav", ".ogg", ".wv") self.create_widgets() self.update_progress_loop() def create_widgets(self): # 顶部文件夹选择 frame_top = ttk.Frame(self.root, padding=10) frame_top.pack(fill=tk.X) ttk.Button(frame_top, text="选择音乐文件夹", command=self.load_folder).pack(side=tk.LEFT) self.folder_var = tk.StringVar(value="未选择文件夹") ttk.Label(frame_top, textvariable=self.folder_var).pack(side=tk.LEFT, padx=10) # 主内容区:左侧歌曲列表,右侧歌词 main_paned = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL) main_paned.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 左侧歌曲列表框架 left_frame = ttk.Frame(main_paned) main_paned.add(left_frame, weight=1) ttk.Label(left_frame, text="播放列表", font=("微软雅黑", 10, "bold")).pack(anchor=tk.W, pady=(0,5)) scroll = ttk.Scrollbar(left_frame) scroll.pack(side=tk.RIGHT, fill=tk.Y) self.listbox = tk.Listbox(left_frame, yscrollcommand=scroll.set, font=("微软雅黑", 11)) scroll.config(command=self.listbox.yview) self.listbox.pack(fill=tk.BOTH, expand=True) self.listbox.bind("<Double-Button-1>", self.double_play) # 右侧歌词显示区域 right_frame = ttk.Frame(main_paned) main_paned.add(right_frame, weight=1) ttk.Label(right_frame, text="LRC 歌词", font=("微软雅黑", 10, "bold")).pack(anchor=tk.W, pady=(0,5)) # 使用Listbox显示歌词,方便高亮 lyric_scroll = ttk.Scrollbar(right_frame) lyric_scroll.pack(side=tk.RIGHT, fill=tk.Y) self.lyric_listbox = tk.Listbox(right_frame, yscrollcommand=lyric_scroll.set, font=("微软雅黑", 10), selectmode=tk.SINGLE, bg="#f8f8f8", fg="#333333") lyric_scroll.config(command=self.lyric_listbox.yview) self.lyric_listbox.pack(fill=tk.BOTH, expand=True) # 进度条区域 frame_progress = ttk.Frame(self.root, padding=10) frame_progress.pack(fill=tk.X) self.time_label = ttk.Label(frame_progress, text="00:00 / 00:00") self.time_label.pack(side=tk.RIGHT) self.progress_bar = ttk.Scale(frame_progress, from_=0, to=0, orient=tk.HORIZONTAL) self.progress_bar.pack(fill=tk.X, padx=(0,10)) # 绑定鼠标事件 self.progress_bar.bind("<ButtonPress-1>", self.drag_start) self.progress_bar.bind("<B1-Motion>", self.drag_moving) self.progress_bar.bind("<ButtonRelease-1>", self.drag_end) # 控制按钮区 frame_btn = ttk.Frame(self.root, padding=10) frame_btn.pack() self.btn_prev = ttk.Button(frame_btn, text="上一首", command=self.play_prev) self.btn_prev.grid(row=0, column=0, padx=5) self.btn_pause = ttk.Button(frame_btn, text="播放", command=self.pause_resume) self.btn_pause.grid(row=0, column=1, padx=5) self.btn_next = ttk.Button(frame_btn, text="下一首", command=self.play_next) self.btn_next.grid(row=0, column=2, padx=5) self.mode_var = tk.StringVar(value="顺序播放") mode_menu = ttk.OptionMenu(frame_btn, self.mode_var, "顺序播放", "顺序播放", "单曲循环", "随机播放", command=self.change_mode) mode_menu.grid(row=0, column=3, padx=15) def change_mode(self, val): mode_map = {"顺序播放": "order", "单曲循环": "loop", "随机播放": "random"} self.play_mode = mode_map[val] def load_folder(self): path = filedialog.askdirectory() if not path: return self.folder_var.set(path) self.music_list.clear() self.listbox.delete(0, tk.END) for fname in os.listdir(path): full_path = os.path.join(path, fname) if full_path.lower().endswith(self.suffixs): self.music_list.append(full_path) self.listbox.insert(tk.END, fname) if not self.music_list: messagebox.showinfo("提示", "文件夹无支持音频") else: self.stop_music() self.current_index = -1 self.clear_lyrics() def clear_lyrics(self): """清空歌词显示""" self.lyric_listbox.delete(0, tk.END) self.lyrics.clear() self.current_lyric_index = -1 def load_lyrics(self, music_path): """加载与音乐文件同名的.lrc文件""" self.clear_lyrics() base = os.path.splitext(music_path)[0] lrc_path = base + ".lrc" if not os.path.exists(lrc_path): self.lyric_listbox.insert(tk.END, " (无歌词文件) ") return try: with open(lrc_path, "r", encoding="utf-8") as f: lines = f.readlines() except UnicodeDecodeError: try: with open(lrc_path, "r", encoding="gbk") as f: lines = f.readlines() except Exception as e: self.lyric_listbox.insert(tk.END, f"歌词文件读取失败: {e}") return # 正则匹配时间标签 [mm:ss.xx] 或 [mm:ss] time_pattern = re.compile(r'\[(\d{2}):(\d{2})(?:\.(\d{2}))?\]') lyric_dict = {} # 用字典收集同一时间的多行歌词 for line in lines: line = line.strip() if not line: continue # 查找所有时间标签 matches = list(time_pattern.finditer(line)) if not matches: continue # 提取歌词文本(去除时间标签后的部分) text = line for m in matches: text = text.replace(m.group(0), "") text = text.strip() if not text: continue for m in matches: minute = int(m.group(1)) sec = int(m.group(2)) cent = m.group(3) if m.group(3) else "00" total_sec = minute * 60 + sec + int(cent) / 100 # 如果同一时间有多行,合并 if total_sec in lyric_dict: lyric_dict[total_sec] += "\n" + text else: lyric_dict[total_sec] = text # 转换为列表并按时间排序 self.lyrics = sorted(lyric_dict.items(), key=lambda x: x[0]) # 填充到Listbox for idx, (ts, text) in enumerate(self.lyrics): display_line = f"[{self.sec_format(ts)}] {text}" self.lyric_listbox.insert(tk.END, display_line) if not self.lyrics: self.lyric_listbox.insert(tk.END, " (无有效歌词行) ") def update_lyrics(self, current_time): """根据当前播放时间高亮对应歌词行""" if not self.lyrics: return # 找到最后一个时间 <= current_time 的歌词行 idx = -1 for i, (ts, _) in enumerate(self.lyrics): if ts <= current_time: idx = i else: break if idx == -1: return if idx != self.current_lyric_index: # 清除之前的高亮 if self.current_lyric_index != -1: self.lyric_listbox.itemconfig(self.current_lyric_index, bg="#f8f8f8", fg="#333333") self.current_lyric_index = idx # 高亮当前行 self.lyric_listbox.itemconfig(idx, bg="#ffff99", fg="#000000") # 滚动到可视区域 self.lyric_listbox.see(idx) def get_duration(self, path): try: if path.endswith(".mp3"): audio = MP3(path) elif path.endswith(".flac"): audio = FLAC(path) elif path.endswith(".ogg"): audio = OggVorbis(path) elif path.endswith(".wv"): audio = WavPack(path) else: return 0 return audio.info.length except Exception: return 0 def sec_format(self, sec): m = int(sec // 60) s = int(sec % 60) return f"{m:02d}:{s:02d}" def double_play(self, event): sel = self.listbox.curselection() if sel: self.play_audio(sel[0], start=0) def play_audio(self, idx, start=0): if not self.music_list: return pygame.mixer.music.stop() time.sleep(0.05) self.current_index = idx self.current_file = self.music_list[idx] pygame.mixer.music.load(self.current_file) pygame.mixer.music.play(start=start) self.is_playing = True self.btn_pause.config(text="暂停") self.music_length = self.get_duration(self.current_file) self.progress_bar.config(to=self.music_length) self.play_start_time = time.time() - start self.progress_bar.set(start) self.time_label.config(text=f"{self.sec_format(start)} / {self.sec_format(self.music_length)}") # 高亮歌曲列表 self.listbox.selection_clear(0, tk.END) self.listbox.selection_set(idx) self.listbox.see(idx) # 加载歌词 self.load_lyrics(self.current_file) def stop_music(self): pygame.mixer.music.stop() self.is_playing = False self.btn_pause.config(text="播放") self.progress_bar.set(0) self.time_label.config(text="00:00 / 00:00") self.play_start_time = 0 self.clear_lyrics() def pause_resume(self): if self.current_index == -1: if self.music_list: self.play_audio(0) return if self.is_playing: self.pause_time = time.time() pygame.mixer.music.pause() self.is_playing = False self.btn_pause.config(text="播放") else: if self.pause_time > 0: pause_duration = time.time() - self.pause_time self.play_start_time += pause_duration self.pause_time = 0 pygame.mixer.music.unpause() self.is_playing = True self.btn_pause.config(text="暂停") def play_prev(self): if not self.music_list: return new_idx = self.current_index - 1 if new_idx < 0: new_idx = len(self.music_list) - 1 self.play_audio(new_idx, start=0) def play_next(self): if not self.music_list: return if self.play_mode == "random": new_idx = random.randint(0, len(self.music_list) - 1) elif self.play_mode == "loop": new_idx = self.current_index else: new_idx = self.current_index + 1 if new_idx >= len(self.music_list): new_idx = 0 self.play_audio(new_idx, start=0) # 拖拽处理 def drag_start(self, event): self.dragging = True self.drag_pos = self.progress_bar.get() def drag_moving(self, event): if self.dragging: self.drag_pos = self.progress_bar.get() self.time_label.config(text=f"{self.sec_format(self.drag_pos)} / {self.sec_format(self.music_length)}") def drag_end(self, event): if not self.dragging or self.music_length <= 0 or self.current_index == -1: self.dragging = False return target_sec = self.progress_bar.get() was_playing = self.is_playing pygame.mixer.music.stop() time.sleep(0.05) pygame.mixer.music.load(self.current_file) pygame.mixer.music.play(start=target_sec) if was_playing: self.is_playing = True self.btn_pause.config(text="暂停") self.play_start_time = time.time() - target_sec self.pause_time = 0 else: pygame.mixer.music.pause() self.is_playing = False self.btn_pause.config(text="播放") self.pause_time = time.time() self.progress_bar.set(target_sec) self.time_label.config(text=f"{self.sec_format(target_sec)} / {self.sec_format(self.music_length)}") self.dragging = False # 拖拽后立即更新歌词显示 self.update_lyrics(target_sec) # 定时刷新循环(进度 + 歌词同步) def update_progress_loop(self): if not self.dragging and self.is_playing and self.music_length > 0 and self.play_start_time > 0: current_pos = time.time() - self.play_start_time if current_pos >= self.music_length: current_pos = self.music_length self.play_next() elif current_pos < 0: current_pos = 0 self.progress_bar.set(current_pos) self.time_label.config(text=f"{self.sec_format(current_pos)} / {self.sec_format(self.music_length)}") # 同步更新歌词 self.update_lyrics(current_pos) self.root.after(100, self.update_progress_loop) if __name__ == "__main__": win = tk.Tk() app = MusicPlayer(win) win.mainloop() -
@ 2026-5-19 19:31:52
少羽NB
- 1