PythonでMP3再生(非同期手法の試行錯誤3)
しばらく実践しないうちに「非同期手法」が頭からすっかり抜けてしまったので「リハビリ」です。
一か月ほど前にこんなの書いていたけど
見返すと、余計なこと書き過ぎていて肝心な点がよくわからない(反省)。
今後、すぐ思い出せるように、今回は要点を簡潔にまとめなおしたいと思います。
MP3再生の基本(GUI無し)
今回は非同期の動作を確認する目的で、できるだけ簡潔なコードにします。テストデータは例によって「甘茶の音楽工房」様の「残業戦士」
です。
import io
import time
import pygame
from pydub import AudioSegment
class MyPlayer:
def __init__(self):
super().__init__()
self.audio = None # pydub AudioSegment
self.mixer = pygame.mixer
self.mixer.init()
# ファイル読み込み
def set_mp3file(self, filename):
self.audio = AudioSegment.from_mp3(filename)
def play(self):
self.audio_buffer = io.BytesIO()
self.audio.export(self.audio_buffer, format="wav")
self.audio_buffer.seek(0)
self.mixer.music.load(self.audio_buffer)
self.mixer.music.play()
while self.mixer.music.get_busy():
time.sleep(0.2)
self.stop()
def stop(self):
self.mixer.music.stop()
# テストコード
if __name__ == "__main__":
myplayer = MyPlayer()
myplayer.set_mp3file("zangyousenshi.mp3")
print("一回目演奏開始")
myplayer.play()
print("一回目演奏終了")
print("二回目演奏開始")
myplayer.play()
print("二回目演奏終了")
これを実行すると、曲が2回再生されます。これが今回の出発点。
GUI(CustomTkinter)と組み合わせる
非同期を考えないで書いてみると
playボタンとstopボタンがあるだけのプレイヤーのGUIで
ソースコードは
import io
import time
import customtkinter as ctk
import pygame
from pydub import AudioSegment
class MyPlayer:
def __init__(self):
super().__init__()
self.audio = None # pydub AudioSegment
self.mixer = pygame.mixer
self.mixer.init()
# ファイル読み込み
def set_mp3file(self, filename):
self.audio = AudioSegment.from_mp3(filename)
def play(self):
self.audio_buffer = io.BytesIO()
self.audio.export(self.audio_buffer, format="wav")
self.audio_buffer.seek(0)
self.mixer.music.load(self.audio_buffer)
self.mixer.music.play()
while self.mixer.music.get_busy():
time.sleep(0.2)
self.stop()
def stop(self):
self.mixer.music.stop()
# テストコード
if __name__ == "__main__":
class App(ctk.CTk):
def __init__(self, title):
super().__init__()
self.title(title)
self.player = MyPlayer()
self.player.set_mp3file("zangyousenshi.mp3")
# widget
# 再生ボタン
ctk.CTkButton(self, text="再生", command=self.player.play).pack(
padx=50, pady=10
)
# 停止ボタン
ctk.CTkButton(self, text="停止", command=self.player.stop).pack(
padx=50, pady=10
)
self.protocol("WM_DELETE_WINDOW", self.on_closing)
self.mainloop()
def on_closing(self):
# 閉じる前にやること
self.player.stop()
# 閉じる
self.destroy()
App("MyMP3Player20241116")
「再生」ボタンをクリックすると音楽は流れるので、できたつもりになって満足していると、実は停止ボタンが効かない・・
曲の演奏が終了するまで、GUIが固まっていて、受け付けないわけです。
playの処理をplay_audioメソッドに移す
def play_audio(self):
self.audio_buffer = io.BytesIO()
self.audio.export(self.audio_buffer, format="wav")
self.audio_buffer.seek(0)
self.mixer.music.load(self.audio_buffer)
self.mixer.music.play()
while self.mixer.music.get_busy():
time.sleep(0.2)
self.stop()
def play(self):
self.play_audio()
非同期を導入する(threading)
from threading import Thread
を追加しておいて、
def play_audio(self):
self.audio_buffer = io.BytesIO()
self.audio.export(self.audio_buffer, format="wav")
self.audio_buffer.seek(0)
self.mixer.music.load(self.audio_buffer)
self.mixer.music.play()
while self.mixer.music.get_busy():
time.sleep(0.2)
self.stop()
def play(self):
# self.play_audio()
thread = Thread(target=self.play_audio)
thread.start()
こう書くとGUIが固まらなくなってGood。
現時点の最適解
今後どうなるかわかりませんが、今の時点ではこれが最適解と思っております。コード書いておきます。
import io
import time
from threading import Thread
import customtkinter as ctk
import pygame
from pydub import AudioSegment
class MyPlayer:
def __init__(self):
super().__init__()
self.audio = None # pydub AudioSegment
self.mixer = pygame.mixer
self.mixer.init()
# ファイル読み込み
def set_mp3file(self, filename):
self.audio = AudioSegment.from_mp3(filename)
def play_audio(self):
self.audio_buffer = io.BytesIO()
self.audio.export(self.audio_buffer, format="wav")
self.audio_buffer.seek(0)
self.mixer.music.load(self.audio_buffer)
self.mixer.music.play()
while self.mixer.music.get_busy():
time.sleep(0.2)
self.stop()
def play(self):
# self.play_audio()
thread = Thread(target=self.play_audio)
thread.start()
def stop(self):
self.mixer.music.stop()
# テストコード
if __name__ == "__main__":
class App(ctk.CTk):
def __init__(self, title):
super().__init__()
self.title(title)
self.player = MyPlayer()
self.player.set_mp3file("zangyousenshi.mp3")
# widget
# 再生ボタン
ctk.CTkButton(self, text="再生", command=self.player.play).pack(
padx=50, pady=10
)
# 停止ボタン
ctk.CTkButton(self, text="停止", command=self.player.stop).pack(
padx=50, pady=10
)
self.protocol("WM_DELETE_WINDOW", self.on_closing)
self.mainloop()
def on_closing(self):
# 閉じる前にやること
self.player.stop()
# 閉じる
self.destroy()
App("MyMP3Player20241116")