高通台灣AI黑客松|競賽說明會
|

台語MP3播放器(Claude + Python):弓蕉(kin)的台語秘密

   

作者: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


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


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


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


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 拆解

  1. Window.py:第六節。使用 Python 標準的 UI 套件 Tkinter 撰寫的視窗(圖 6)。
  2. UI.py:第七節。在 Window.py 裡頭擺放各種 UI 元件(圖 7)。
  3. Player.py:第八節。使用 Pygame 套件來播放 MP3 音檔(圖 8)。

Window.py


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


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


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


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 字詞內容為:

  1. 請你講,你莫烏白呸喙瀾!
  2. 魔神仔,聽講深山林內有足濟(tsēre)魔神仔。
  3. 便看,欲(berh)按怎做,等對方條件開出來才便看。
  4. 歇熱,恁學校當時開始歇熱?
  5. ,我的代誌,你莫烏白去宣傳。
  6. 出業讀冊讀半死 出業做苦力 (ku-lí)。
  7. 橫(huînn)直,毋管你按怎講,橫(huînn)直我毋信。
  8. 順紲,你若欲(berh)去(khìr)買菜,順紲去(khìr)共我納電火(hér)錢。
  9. 抑無,你若是無法度一擺總納,抑無,看欲(berh)分做幾期?
  10. 毋是勢,等一下看毋是勢,你就緊走。
  11. 明年,伊明年就出業矣。
  12. 賴賴趖,救蟲,賴賴趖;救人,無功勞。
  13. 搜揣(tshēr),欲(berh)了解這个議題(terê),你聽(thing)好先上網搜揣(tshēr)相關的資料。
  14. 允准,無我的允准,你袂(buē)使來阮兜。
  15. 拍毋見,昨日我拍毋見一千箍。
  16.  ,法院的判決無公平(pînn),伊欲(berh)閣上訴。
  17. 夠額,阮公司的員工已經夠額,無欲(berh)閣倩矣。
  18. 拄著,伊近(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輕鬆學》。其中,帳密的申請請讀者 自行接洽全華業務。

(作者為本刊專欄作家,本文同步表於作者部落格,原文連結;責任編輯:謝涵如)

Ted Lee

訂閱MakerPRO知識充電報

與40000位開發者一同掌握科技創新的技術資訊!

Author: Ted Lee

從工程師轉任中學教師,又為了捍衛教育理念,投身成為 STEAM 教育工作者,自稱「無可救藥的人文教育理想主義者」的李俊德(Ted Lee)。

Share This Post On
468 ad

Submit a Comment

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *