greatghoul/greatghoul.github.io

使用 ChatGPT 写一个简单的播放器

Opened this issue · 0 comments


slug: chatgpt-music-player
date: 2023-05-24

作为一个普通程序员,听歌可以说是我的日常了,工作时听,学习时听,游戏时也要听。

我的听歌渠道

我近几年没有使用网易云音乐的习惯,觉得杂音太多。

Youtube

一般用于听一些老歌集锦,有种上古时代听 CD 或者磁带的感觉。也会听一些动物吃东西的 ASMR。而且 Youtube 的推荐算是听符合口味了。

image

image

本地音乐

会通过一些网站下载喜欢的歌曲到本地磁盘,然后用播放器循环播放,我最常听的音乐都放在 Google Drive 里面,然后再电脑和手机上同步。

电脑上使用 Google Drive 自己的客户端同步,手机上是使用 FolderSync Pro,就同步而言,可以说是很丝滑了。

听歌应用的选择,手机上使用的是 Musicolet ,算是比较干净,体验也很好,电脑上面则没有什么好的选择,试了一些播放器,都设计的太复杂了,最近一段时间,使用的是 VLC。

image

自己动手

之前我试用过很多音乐播放器,无论是桌面的,浏览器扩展,还是网页应用,都没有能特别满足我的。

网页办的应用,有使用过 GDriveMusic,直接从 GDrive 读取文件夹,网页里面播放,省去了同步到本地的麻烦,但是这个服务偶尔会抽风。最后不得已弃用了。不过它的简洁风格,我是很喜欢的。

image

最近一段时间,AI 大行其道,于是我萌生了让 AI 帮我写一个播放器的想法。我选择的开发工具是 Python 的 Tkinter,因为 Python 自带,没有什么特殊的依赖,我对 UI 精美程度没有太高的期待,所以功能够用就好。

起初我只是简单的帮我用 tkinter 写一个音乐播放器,加载给定的文件夹里面的歌曲。然后 AI 给出的代码竟然直接就可以运行。

于是我就再按照自己的要求,调整了一下,它就给我生成了一段 Python 代码,运行后是一个大差不差的应用。

write a simple mp3 player in Tkinter, should include a simple playlist from folder “G:\我的云端硬盘\01 音乐” , it plays music in the playlist randomly until app exit, or user can double click a music to start playing it, the playlist only show the music information in “artist - title” format, if both artist and title are missing, use the filename instead, no need duration column, music in playing should be in bold font style. The app don’t have a menubar, instead a toolbar including play / pause / stop / next buttons will be shown at the foot of the window.

image

但是它其实没有正确处理自动播放下一曲的功能,这也难不住 AI,直接问它 pygame mixer 怎么检查歌曲播放结束,它给出了类似下面的示例代码。

    def check_music_end(self):
        if pygame.mixer.music.get_busy():
            self.root.after(1000, self.check_music_end)
        elif pygame.mixer.music.queue:
            self.next_music()

稍微加入到代码中,调整调整,就正确的运行了。

我自己又在生成的代码上微调了一下布局什么的,一个拥有小巧窗口的播放器就完成了,只有简单的播放列表和控制按钮,支持自动随机播放。

image

把 python 文件的后缀改为 .pyw 这样双击运行后不会有 Window 控制台的黑窗口,一切都很完美。

最终代码如下。以后想加什么进度条之类的,可以继续通过 AI 调教它。

import os
import random
import tkinter as tk
from pygame import mixer

class MusicPlayer:
    def __init__(self, root):
        self.manually_stopped = False
        self.root = root
        self.root.title("Simple MP3 Player")
        self.root.geometry("500x400")

        # initialize mixer
        mixer.init()

        # create playlist
        self.music_folder = r"G:\\我的云端硬盘\\01 音乐"
        self.playlist = []
        for file in os.listdir(self.music_folder):
            if file.endswith(".mp3"):
                self.playlist.append(file)

        # create playlist frame
        self.playlist_frame = tk.Frame(self.root)
        self.playlist_frame.pack(fill="both", expand=1, padx=10, pady=10)

        # create playlist treeview
        self.playlist_treeview = tk.Listbox(
            self.playlist_frame,
            selectmode=tk.SINGLE
        )
        
        # create playlist scrollbar
        self.playlist_scrollbar = tk.Scrollbar(self.playlist_frame, command=self.playlist_treeview.yview)
        self.playlist_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.playlist_treeview.config(yscrollcommand=self.playlist_scrollbar.set)

        self.playlist_treeview.pack(fill="both", expand=1)

        # set playlist items
        for file in self.playlist:
            self.playlist_treeview.insert(tk.END, file.split(".mp3")[0])

        # bind click on playlist item
        self.playlist_treeview.bind("<Button-1>", self.play_music)

        # create control frame
        self.control_frame = tk.Frame(self.root)
        self.control_frame.pack(fill="both", expand=0, padx=10, pady=(0, 10))

        # create control buttons
        self.play_button = tk.Button(self.control_frame, text="Play", command=self.play_music)
        self.play_button.pack(side=tk.LEFT, padx=10)

        self.pause_button = tk.Button(self.control_frame, text="Pause", command=self.pause_music)
        self.pause_button.pack(side=tk.LEFT, padx=10)

        self.stop_button = tk.Button(self.control_frame, text="Stop", command=self.stop_music)
        self.stop_button.pack(side=tk.LEFT, padx=10)

        self.next_button = tk.Button(self.control_frame, text="Next", command=self.next_music)
        self.next_button.pack(side=tk.LEFT, padx=10)

        # set current music index
        self.current_music_index = None

        # play the first music
        self.next_music()

    def play_music(self, event=None):
        if event:
            self.current_music_index = self.playlist_treeview.curselection()[0]
        if self.current_music_index is None:
            self.current_music_index = random.randint(0, len(self.playlist) - 1)
        mixer.music.load(os.path.join(self.music_folder, self.playlist[self.current_music_index]))
        mixer.music.play()
        self.playlist_treeview.selection_clear(0, tk.END)
        self.playlist_treeview.selection_set(self.current_music_index)

        self.check_music_end()

    def check_music_end(self):
        if self.manually_stopped:
          return

        if mixer.music.get_busy():
            self.root.after(1000, self.check_music_end)
        elif mixer.music.queue:
            self.next_music()
            
    def pause_music(self):
        mixer.music.pause()
        self.manually_stopped = True
        self.pause_button.configure(text="Resume", command=self.resume_music)
    
    def resume_music(self):
        mixer.music.unpause()
        self.manually_stopped = False
        self.pause_button.configure(text="Pause", command=self.pause_music)

    def stop_music(self):
        mixer.music.stop()
        self.manually_stopped = True
        self.current_music_index = None
        self.playlist_treeview.itemconfig(self.playlist_treeview.curselection(), bg="white")

    def next_music(self):
        if self.current_music_index is not None:
            self.playlist_treeview.itemconfig(self.current_music_index, bg="white")
        self.current_music_index = random.randint(0, len(self.playlist) - 1)
        self.playlist_treeview.see(self.current_music_index)
        self.play_music()

if __name__ == "__main__":
    root = tk.Tk()
    app = MusicPlayer(root)
    root.mainloop()