作者:Ted Lee
1979 年,在高雄某處的弓[2] 蕉園裡,埋藏了一樁天大的秘密。根據當年幸運逃出的生還者口述,那片偌大弓蕉園的深處,由一條身長數尺的「Python」千年來一直守護著一件傳說中的上古兵器「Claude」。江湖中早有傳言:「得 Claude 者得天下。」也就是這樣,在那年的某個夜晚,一群群如海水般的人潮不斷地來到了弓蕉園,開始了一場腥風血雨的人間殺戮。到底是誰最後能找到 Claude 的隱身之處呢?能解開這個謎團的,據說是一位操有標準老泉腔的少年英雄……
用哪個 ChatGPT 來生?
世界上 GPT 百百款,為何我們獨挑 Claude 這枝花?其實,根據先前使用 Copilot 生成 micro:bit Python 程式碼的經驗,我們最早是在 Copilot 上下以下咒語來生的:
(人設)你是 Python 程式專家
(任務)Tkinter 視窗版的 MP3 播放器,選擇多首 MP3 曲目後,顯示在畫面上,連續隨機播放這些曲目不停止。正在播放的曲目要用顏色標記
(輸出)Python 程式碼,要含詳細註解,並生成 Mermaid 流程圖
但是,天不從人願。工具總是有其侷限性:有一好就不一定有二好。Copilot 生出的程式碼,再怎麼叫它修改就是一直不能「自動播放」。
正當準備放棄之際,突然靈光一現:把程式碼貼給其他版本的 GPT 來幫助抓蟲呢?
於是乎產出了以下符合需求的程式碼(在陳會安老師設計的 fChart Thonny 上的執行畫面如圖 1):
MP3 Player_Coipilot+Claude.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 import tkinter as tk
from tkinter import filedialog
import pygame
import random
import os
# 初始化 Pygame 的混音器
pygame.mixer.init()
# 建立主視窗
root = tk.Tk()
root.title("弓蕉園的秘密 字詞放送 Powered by 李俊德 Lí Tsùn-ti")
# 取得螢幕寬度和高度
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# 設定視窗大小為螢幕的 70%
window_width = int(screen_width * 0.7)
window_height = int(screen_height * 0.7)
# 計算視窗的初始位置,使其位於畫面的中上方
x_position = int((screen_width - window_width) / 2)
y_position = int(screen_height * 0.1) # 設定為螢幕高度的 10% 處
# 設定視窗的位置和大小
root.geometry(f"{window_width}x{window_height}+{x_position}+{y_position}")
# 儲存選擇的檔案路徑和播放狀態
file_paths = []
current_index = 0
is_paused = False
is_playing = False
def select_files():
global file_paths, current_index
# 打開文件對話框讓用戶選擇 MP3 檔案
new_files = list(filedialog.askopenfilenames(filetypes=[("MP3 Files", "*.mp3")]))
if new_files:
file_paths = new_files
# 隨機排列播放順序
random.shuffle(file_paths)
current_index = 0
update_file_listbox()
load_and_play_current()
def load_and_play_current():
global is_playing
if file_paths:
# 加載當前 MP3 檔案
pygame.mixer.music.load(file_paths[current_index])
# 播放音樂
pygame.mixer.music.play()
is_playing = True
highlight_current_track()
def pause_music():
global is_paused
if not is_playing:
return
if is_paused:
pygame.mixer.music.unpause()
pause_button.config(text="暫停")
else:
pygame.mixer.music.pause()
pause_button.config(text="繼續")
is_paused = not is_paused
def exit_program():
pygame.mixer.music.stop()
root.destroy()
def play_next():
global current_index, is_playing
if not file_paths:
return
current_index = (current_index + 1) % len(file_paths)
if is_playing:
load_and_play_current()
def update_file_listbox():
file_listbox.delete(0, tk.END)
for path in file_paths:
# 只顯示檔案名稱而不是完整路徑
filename = os.path.basename(path)
file_listbox.insert(tk.END, filename)
def highlight_current_track():
for i in range(file_listbox.size()):
if i == current_index:
file_listbox.itemconfig(i, {'bg':'yellow'})
else:
file_listbox.itemconfig(i, {'bg':'white'})
def check_music_end():
if is_playing and not pygame.mixer.music.get_busy() and not is_paused:
# 當前曲目播放結束且不是暫停狀態時,播放下一首
play_next()
# 每 100 毫秒檢查一次播放狀態
root.after(100, check_music_end)
# 建立選擇檔案按鈕
select_button = tk.Button(root, text="選擇 MP3 檔案", command=select_files)
select_button.pack(pady=10)
# 顯示選擇的檔案路徑
file_listbox = tk.Listbox(root, selectmode=tk.SINGLE)
file_listbox.pack(pady=10, fill=tk.BOTH, expand=True)
# 建立暫停按鈕
pause_button = tk.Button(root, text="暫停", command=pause_music)
pause_button.pack(pady=10)
# 建立結束按鈕,放在右上方
exit_button = tk.Button(root, text="結束", command=exit_program)
exit_button.place(relx=1.0, x=-10, y=10, anchor='ne')
# 啟動檢查播放結束事件的循環
root.after(100, check_music_end)
# 啟動主迴圈
root.mainloop()

圖 1:Copilot+Claude 生成的「弓蕉園的秘密 字詞放送(MP3 Player_Coipilot+Claude.py)」播放器
因此,我們得到了一個結論:
哪個 GPT 最好:混成的(hybrid)[3] 上讚啦!
Claude 生成的程式碼
將以上的相同咒語輸入到 Claude 中,我們可以得到下列以物件導向程式(Object-Oriented Programming,OOP)風格的程式碼,其執行的畫面如圖 2 所示。
OOP.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114 import tkinter as tk
from tkinter import ttk, filedialog
import pygame
import random
import os
#from mutagen.mp3 import MP3
from threading import Thread
import time
class MP3Player:
def __init__(self):
# 初始化播放器
pygame.mixer.init()
# 創建主窗口
self.root = tk.Tk()
self.root.title("MP3 播放器")
self.root.geometry("1700x1300")
# 播放列表相關變量
self.playlist = [] # 儲存音樂文件路徑
self.current_playing = None # 當前播放的索引
self.is_playing = False
self.setup_ui()
self.setup_threads()
def setup_ui(self):
"""設置用戶界面"""
# 創建按鈕框架
button_frame = ttk.Frame(self.root)
button_frame.pack(pady=10)
# 添加控制按鈕
ttk.Button(button_frame, text="添加音樂", command=self.add_music).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="開始播放", command=self.start_playing).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="停止播放", command=self.stop_playing).pack(side=tk.LEFT, padx=5)
# 創建播放列表
self.playlist_box = tk.Listbox(self.root, width=70, height=20)
self.playlist_box.pack(pady=10)
# 創建當前播放標籤
self.current_label = ttk.Label(self.root, text="當前未播放")
self.current_label.pack(pady=10)
def setup_threads(self):
"""設置後台線程"""
self.playing_thread = Thread(target=self.playing_loop, daemon=True)
self.playing_thread.start()
def add_music(self):
"""添加音樂文件到播放列表"""
files = filedialog.askopenfilenames(
filetypes=[("MP3 Files", "*.mp3")],
title="選擇音樂文件"
)
for file in files:
self.playlist.append(file)
# 只顯示文件名而不是完整路徑
self.playlist_box.insert(tk.END, os.path.basename(file))
def start_playing(self):
"""開始播放"""
if not self.is_playing and self.playlist:
self.is_playing = True
def stop_playing(self):
"""停止播放"""
self.is_playing = False
pygame.mixer.music.stop()
self.current_playing = None
self.update_current_playing()
def playing_loop(self):
"""播放循環(在後台線程中運行)"""
while True:
if self.is_playing and not pygame.mixer.music.get_busy():
if self.playlist:
# 隨機選擇下一首
next_index = random.randint(0, len(self.playlist) - 1)
# 加載並播放音樂
try:
pygame.mixer.music.load(self.playlist[next_index])
pygame.mixer.music.play()
self.current_playing = next_index
self.update_current_playing()
except:
print(f"無法播放文件: {self.playlist[next_index]}")
time.sleep(0.1) # 避免過度佔用 CPU
def update_current_playing(self):
"""更新界面顯示當前播放歌曲"""
# 重置所有項目的顏色
for i in range(self.playlist_box.size()):
self.playlist_box.itemconfig(i, {'bg': 'white'})
if self.current_playing is not None:
# 設置當前播放項目的背景色
self.playlist_box.itemconfig(self.current_playing, {'bg': 'light blue'})
self.current_label.config(text=f"正在播放: {os.path.basename(self.playlist[self.current_playing])}")
else:
self.current_label.config(text="當前未播放")
def run(self):
"""運行應用程序"""
self.root.mainloop()
if __name__ == "__main__":
player = MP3Player()
player.run()

圖 2:OOP.py 的執行畫面
程式碼簡化1:非OOP版
我們讓 Claude 將第三小節的 OOP 方式改成非 OOP 程式如下,其執行畫面如圖 3 所示。
Non-OOP.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135 import tkinter as tk
from tkinter import ttk, filedialog
import pygame
import random
import os
#from mutagen.mp3 import MP3
from threading import Thread
import time
# 全域變數
playlist = [] # 儲存音樂檔案路徑
current_playing = None # 當前播放的索引
is_playing = False # 播放狀態
def setup_ui(root, playlist_box, current_label):
"""設置使用者介面"""
# 建立按鈕框架
button_frame = ttk.Frame(root)
button_frame.pack(pady=10)
# 新增控制按鈕
ttk.Button(
button_frame,
text="新增音樂",
command=lambda: add_music(playlist_box)
).pack(side=tk.LEFT, padx=5)
ttk.Button(
button_frame,
text="開始播放",
command=lambda: start_playing()
).pack(side=tk.LEFT, padx=5)
ttk.Button(
button_frame,
text="停止播放",
command=lambda: stop_playing(playlist_box, current_label)
).pack(side=tk.LEFT, padx=5)
def add_music(playlist_box):
"""新增音樂檔案到播放清單"""
files = filedialog.askopenfilenames(
filetypes=[("MP3 Files", "*.mp3")],
title="選擇音樂檔案"
)
for file in files:
playlist.append(file)
# 只顯示檔案名而不是完整路徑
playlist_box.insert(tk.END, os.path.basename(file))
def start_playing():
"""開始播放"""
global is_playing
if not is_playing and playlist:
is_playing = True
def stop_playing(playlist_box, current_label):
"""停止播放"""
global is_playing, current_playing
is_playing = False
pygame.mixer.music.stop()
current_playing = None
update_current_playing(playlist_box, current_label)
def update_current_playing(playlist_box, current_label):
"""更新介面顯示當前播放歌曲"""
# 重置所有項目的顏色
for i in range(playlist_box.size()):
playlist_box.itemconfig(i, {'bg': 'white'})
if current_playing is not None:
# 設置當前播放項目的背景色
playlist_box.itemconfig(current_playing, {'bg': 'light blue'})
current_label.config(
text=f"正在播放: {os.path.basename(playlist[current_playing])}"
)
else:
current_label.config(text="當前未播放")
def playing_loop(playlist_box, current_label):
"""播放循環(在背景執行緒中執行)"""
global current_playing
while True:
if is_playing and not pygame.mixer.music.get_busy():
if playlist:
# 隨機選擇下一首
next_index = random.randint(0, len(playlist) - 1)
# 載入並播放音樂
try:
pygame.mixer.music.load(playlist[next_index])
pygame.mixer.music.play()
current_playing = next_index
update_current_playing(playlist_box, current_label)
except Exception as e:
print(f"無法播放檔案: {playlist[next_index]}")
print(f"錯誤訊息: {str(e)}")
time.sleep(0.1) # 避免過度佔用 CPU
def main():
"""主程式"""
# 初始化播放器
pygame.mixer.init()
# 建立主視窗
root = tk.Tk()
root.title("MP3 播放器")
root.geometry("1200x1000")
# 建立播放清單
playlist_box = tk.Listbox(root, width=70, height=20)
playlist_box.pack(pady=10)
# 建立當前播放標籤
current_label = ttk.Label(root, text="當前未播放")
current_label.pack(pady=10)
# 設置使用者介面
setup_ui(root, playlist_box, current_label)
# 啟動播放執行緒
playing_thread = Thread(
target=lambda: playing_loop(playlist_box, current_label),
daemon=True
)
playing_thread.start()
# 開始主迴圈
root.mainloop()
if __name__ == "__main__":
main()

圖 3:Non-OOP.py 的執行畫面。
程式碼簡化 2:非多執行緒版
我再讓 Claude 將第四小節的非 OOP 版本以非多執行緒的方式產生如下的程式碼,以便進行第六到八小節的細部拆解[4]。
No-threading.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 import tkinter as tk
from tkinter import ttk, filedialog
import pygame
import random
import os
# 全域變數
playlist = [] # 儲存音樂檔案路徑
current_playing = None # 當前播放的索引
is_playing = False # 播放狀態
def setup_ui(root, playlist_box, current_label):
"""設置使用者介面"""
# 建立按鈕框架
button_frame = ttk.Frame(root)
button_frame.pack(pady=10)
# 新增控制按鈕
ttk.Button(
button_frame,
text="新增音樂",
command=lambda: add_music(playlist_box)
).pack(side=tk.LEFT, padx=5)
ttk.Button(
button_frame,
text="開始播放",
command=lambda: start_playing()
).pack(side=tk.LEFT, padx=5)
ttk.Button(
button_frame,
text="停止播放",
command=lambda: stop_playing(playlist_box, current_label)
).pack(side=tk.LEFT, padx=5)
def add_music(playlist_box):
"""新增音樂檔案到播放清單"""
files = filedialog.askopenfilenames(
filetypes=[("MP3 Files", "*.mp3")],
title="選擇音樂檔案"
)
for file in files:
playlist.append(file)
# 只顯示檔案名而不是完整路徑
playlist_box.insert(tk.END, os.path.basename(file))
def start_playing():
"""開始播放"""
global is_playing
if not is_playing and playlist:
is_playing = True
def stop_playing(playlist_box, current_label):
"""停止播放"""
global is_playing, current_playing
is_playing = False
pygame.mixer.music.stop()
current_playing = None
update_current_playing(playlist_box, current_label)
def update_current_playing(playlist_box, current_label):
"""更新介面顯示當前播放歌曲"""
# 重置所有項目的顏色
for i in range(playlist_box.size()):
playlist_box.itemconfig(i, {'bg': 'white'})
if current_playing is not None:
# 設置當前播放項目的背景色
playlist_box.itemconfig(current_playing, {'bg': 'light blue'})
current_label.config(
text=f"正在播放: {os.path.basename(playlist[current_playing])}"
)
else:
current_label.config(text="當前未播放")
def check_and_play(root, playlist_box, current_label):
"""檢查並播放音樂(替代原本的執行緒)"""
global current_playing
if is_playing and not pygame.mixer.music.get_busy():
if playlist:
# 隨機選擇下一首
next_index = random.randint(0, len(playlist) - 1)
# 載入並播放音樂
try:
pygame.mixer.music.load(playlist[next_index])
pygame.mixer.music.play()
current_playing = next_index
update_current_playing(playlist_box, current_label)
except Exception as e:
print(f"無法播放檔案: {playlist[next_index]}")
print(f"錯誤訊息: {str(e)}")
# 每100毫秒檢查一次
root.after(100, lambda: check_and_play(root, playlist_box, current_label))
def main():
"""主程式"""
# 初始化播放器
pygame.mixer.init()
# 建立主視窗
root = tk.Tk()
root.title("MP3 播放器")
root.geometry("1200x1000")
# 建立播放清單
playlist_box = tk.Listbox(root, width=70, height=20)
playlist_box.pack(pady=10)
# 建立當前播放標籤
current_label = ttk.Label(root, text="當前未播放")
current_label.pack(pady=10)
# 設置使用者介面
setup_ui(root, playlist_box, current_label)
# 啟動檢查播放狀態的循環
check_and_play(root, playlist_box, current_label)
# 開始主迴圈
root.mainloop()
if __name__ == "__main__":
main()
程式碼拆解 1:開視窗
寫程式就像是玩拼圖(圖 4),最後產出的軟體專案也都是由一小塊、一小塊的程式積木拼湊而成的。

圖 4:程式如拼圖
因此,我們依續將第二節的原始專案 MP3 Player_Copilot+Claude.py 細部分解如圖 5 所示:

圖 5:MP3 Player_Copilot+Claude.py 拆解
- Window.py:第六節。使用 Python 標準的 UI 套件 Tkinter 撰寫的視窗(圖 6)。
- UI.py:第七節。在 Window.py 裡頭擺放各種 UI 元件(圖 7)。
- Player.py:第八節。使用 Pygame 套件來播放 MP3 音檔(圖 8)。
Window.py
1
2
3
4
5
6
7
8
9
10
11
12
13 import tkinter as tk
from tkinter import ttk
def main():
root = tk.Tk()
root.title("MP3 播放器")
root.geometry("1200x1000")
# 開始主迴圈
root.mainloop()
if __name__ == "__main__":
main()

圖 6:Window.py 開出來的視窗畫面
程式碼拆解 2:UI 佈局
UI.py 使用了包含在 Python 的 Tkinter(Tools Kit Interface)視窗套件(package)[5]中的TTK(Themed Tkinter)及 File Dialog 模組(module),我們將整個程式邏輯佈局以圖 7 的紅字註解來標示。

圖 7:視窗程式的 UI 佈局設計
UI.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 import tkinter as tk
from tkinter import ttk, filedialog
import pygame
import random
import os
#from mutagen.mp3 import MP3
from threading import Thread
import time
# 全域變數
playlist = [] # 儲存音樂檔案路徑
current_playing = None # 當前播放的索引
is_playing = False # 播放狀態
def setup_ui(root, playlist_box, current_label):
"""設置使用者介面"""
# 建立按鈕框架
button_frame = ttk.Frame(root)
button_frame.pack(pady=10)
# 新增控制按鈕
ttk.Button(
button_frame,
text="新增音樂",
command=lambda: add_music(playlist_box)
).pack(side=tk.LEFT, padx=5)
ttk.Button(
button_frame,
text="開始播放",
command=lambda: start_playing()
).pack(side=tk.LEFT, padx=5)
ttk.Button(
button_frame,
text="停止播放",
command=lambda: stop_playing(playlist_box, current_label)
).pack(side=tk.LEFT, padx=5)
def add_music(playlist_box):
"""新增音樂檔案到播放清單"""
pass
def start_playing():
"""開始播放"""
pass
def stop_playing(playlist_box, current_label):
"""停止播放"""
pass
def main():
"""主程式"""
# 初始化播放器
pygame.mixer.init()
# 建立主視窗
root = tk.Tk()
root.title("MP3 播放器")
root.geometry("1200x1000")
# 建立播放清單
playlist_box = tk.Listbox(root, width=70, height=20)
playlist_box.pack(pady=10)
# 建立當前播放標籤
current_label = ttk.Label(root, text="當前未播放")
current_label.pack(pady=10)
# 設置使用者介面
setup_ui(root, playlist_box, current_label)
# 開始主迴圈
root.mainloop()
if __name__ == "__main__":
main()
程式碼拆解 3:MP3 播放
更進一步地,我們在 Claude 下達以下咒語:
抽出程式碼中使用 pygame 來播放 mp3 檔最基本的程式碼,不用 UI
它就能將 MP3 Player_Coipilot+Claude.py 當中使用 Pyagme 套件來播放 MP3 音檔的原型抽取為下列的 Player.py,其執行畫面如圖 8 所示。
Player.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 import pygame
import time
def play_mp3(file_path):
"""播放單一 MP3 檔案"""
# 初始化 pygame mixer
pygame.mixer.init()
try:
# 載入音樂檔案
pygame.mixer.music.load(file_path)
# 開始播放
pygame.mixer.music.play()
# 等待音樂播放完畢
while pygame.mixer.music.get_busy():
time.sleep(0.1)
except Exception as e:
print(f"播放時發生錯誤: {str(e)}")
finally:
# 結束播放
pygame.mixer.music.stop()
pygame.mixer.quit()
if __name__ == "__main__":
# 使用範例
file_path = "18.拄著.mp3" # 替換成您的 MP3 檔案路徑
play_mp3(file_path)

圖 8:Player.py 的執行畫面
功能擴充 1:暫停
圖 2 或圖 3 以 Claude 自動生成的程式碼的結果是比較符合我們想要的結果(比起圖 1 來說)。
於是,我們更進一步追問 Claude:
再加上一個「暫停」按鈕,按一下暫停播放,再按一下重上一個暫停點續播
Claude 立刻生出 Pause.py 了(圖 9)。
Pause.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161 import tkinter as tk
from tkinter import ttk, filedialog
import pygame
import random
import os
#from mutagen.mp3 import MP3
from threading import Thread
import time
# 全域變數
playlist = [] # 儲存音樂檔案路徑
current_playing = None # 當前播放的索引
is_playing = False # 播放狀態
is_paused = False # 暫停狀態
def setup_ui(root, playlist_box, current_label):
"""設置使用者介面"""
# 建立按鈕框架
button_frame = ttk.Frame(root)
button_frame.pack(pady=10)
# 新增控制按鈕
ttk.Button(
button_frame,
text="新增音樂",
command=lambda: add_music(playlist_box)
).pack(side=tk.LEFT, padx=5)
ttk.Button(
button_frame,
text="開始播放",
command=lambda: start_playing()
).pack(side=tk.LEFT, padx=5)
ttk.Button(
button_frame,
text="暫停/繼續",
command=lambda: toggle_pause(current_label)
).pack(side=tk.LEFT, padx=5)
ttk.Button(
button_frame,
text="停止播放",
command=lambda: stop_playing(playlist_box, current_label)
).pack(side=tk.LEFT, padx=5)
def toggle_pause(current_label):
"""切換暫停/繼續狀態"""
global is_paused
if is_playing: # 只有在播放狀態才能暫停/繼續
is_paused = not is_paused
if is_paused:
pygame.mixer.music.pause()
current_label.config(
text=f"已暫停: {os.path.basename(playlist[current_playing])}"
)
else:
pygame.mixer.music.unpause()
current_label.config(
text=f"正在播放: {os.path.basename(playlist[current_playing])}"
)
def add_music(playlist_box):
"""新增音樂檔案到播放清單"""
files = filedialog.askopenfilenames(
filetypes=[("MP3 Files", "*.mp3")],
title="選擇音樂檔案"
)
for file in files:
playlist.append(file)
# 只顯示檔案名而不是完整路徑
playlist_box.insert(tk.END, os.path.basename(file))
def start_playing():
"""開始播放"""
global is_playing, is_paused
if not is_playing and playlist:
is_playing = True
is_paused = False # 重置暫停狀態
def stop_playing(playlist_box, current_label):
"""停止播放"""
global is_playing, current_playing, is_paused
is_playing = False
is_paused = False
pygame.mixer.music.stop()
current_playing = None
update_current_playing(playlist_box, current_label)
def update_current_playing(playlist_box, current_label):
"""更新介面顯示當前播放歌曲"""
# 重置所有項目的顏色
for i in range(playlist_box.size()):
playlist_box.itemconfig(i, {'bg': 'white'})
if current_playing is not None:
# 設置當前播放項目的背景色
playlist_box.itemconfig(current_playing, {'bg': 'light blue'})
status = "已暫停" if is_paused else "正在播放"
current_label.config(
text=f"{status}: {os.path.basename(playlist[current_playing])}"
)
else:
current_label.config(text="當前未播放")
def playing_loop(playlist_box, current_label):
"""播放循環(在背景執行緒中執行)"""
global current_playing
while True:
if is_playing and not is_paused and not pygame.mixer.music.get_busy():
if playlist:
# 隨機選擇下一首
next_index = random.randint(0, len(playlist) - 1)
# 載入並播放音樂
try:
pygame.mixer.music.load(playlist[next_index])
pygame.mixer.music.play()
current_playing = next_index
update_current_playing(playlist_box, current_label)
except Exception as e:
print(f"無法播放檔案: {playlist[next_index]}")
print(f"錯誤訊息: {str(e)}")
time.sleep(0.1) # 避免過度佔用 CPU
def main():
"""主程式"""
# 初始化播放器
pygame.mixer.init()
# 建立主視窗
root = tk.Tk()
root.title("MP3 播放器")
root.geometry("1400x1200")
# 建立播放清單
playlist_box = tk.Listbox(root, width=70, height=20)
playlist_box.pack(pady=10)
# 建立當前播放標籤
current_label = ttk.Label(root, text="當前未播放")
current_label.pack(pady=10)
# 設置使用者介面
setup_ui(root, playlist_box, current_label)
# 啟動播放執行緒
playing_thread = Thread(
target=lambda: playing_loop(playlist_box, current_label),
daemon=True
)
playing_thread.start()
# 開始主迴圈
root.mainloop()
if __name__ == "__main__":
main()

圖 9:Pause.py 的執行畫面
功能擴充 2:動態顯示詞條內容
我們使用本文生成的播放器所播放的台語泉腔發音的 18 字詞內容為:
- 請你莫講,你莫烏白呸喙瀾!
- 魔神仔,聽講深山林內有足濟(tsēre)魔神仔。
- 便看,欲(berh)按怎做,等對方條件開出來才便看。
- 歇熱,恁學校當時開始歇熱?
- 傳,我的代誌,你莫烏白去宣傳。
- 出業,讀冊讀半死 出業做苦力 (ku-lí)。
- 橫(huînn)直,毋管你按怎講,橫(huînn)直我毋信。
- 順紲,你若欲(berh)去(khìr)買菜,順紲去(khìr)共我納電火(hér)錢。
- 抑無,你若是無法度一擺總納,抑無,看欲(berh)分做幾期?
- 毋是勢,等一下看毋是勢,你就緊走。
- 明年,伊明年就出業矣。
- 賴賴趖,救蟲,賴賴趖;救人,無功勞。
- 搜揣(tshēr),欲(berh)了解這个議題(terê),你聽(thing)好先上網搜揣(tshēr)相關的資料。
- 允准,無我的允准,你袂(buē)使來阮兜。
- 拍毋見,昨日我拍毋見一千箍。
- 判 ,法院的判決無公平(pînn),伊欲(berh)閣上訴。
- 夠額,阮公司的員工已經夠額,無欲(berh)閣倩矣。
- 拄著,伊近(kūn)來定定去拄著麻煩。
如果能在圖 9 的畫面加上播放音檔時能自動顯示對應的字詞和其例句,定能使這個播放器更精緻實用。這部份就留給讀者們去找 Claude 協助囉。如果您能成功完成這個任務,也歡迎留言分享給我們!
[1]六種授權條款。
[2]華語的「香蕉」,在台語漢字是念及書寫成「弓蕉」。而「弓」的白音是 king。台灣各地在不同音變下,本文是使用筆者自小的「新─老泉腔」語感而念成「kin」(老泉則以央元音 ir 而發成「kirn」)。
[3]除了 OpenAI 的版本外,目前較常見的 GPT 版本有:Google Gemini、MS Copilot、Claude、Chat Everywhere……。
[4]程式的拆解技巧可參考拙著《Python玩AI,你也可以 — 從CVZone入門吧!》。
[5]Tkinter 的教材可參考拙著《Scratch到Python輕鬆學》。其中,帳密的申請請讀者 自行接洽全華業務。
(作者為本刊專欄作家,本文同步表於作者部落格,原文連結;責任編輯:謝涵如)
- 使用 Maker Uno 快速打造 Arduino入門成功經驗 [0] - 2025/07/30
- 【Ted Lee】莫閣比(micro:bit)台語播放機 - 2025/07/04
- 用GenAI生成連連看樣板 - 2025/05/15
訂閱MakerPRO知識充電報
與40000位開發者一同掌握科技創新的技術資訊!