0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

オーディオプレイヤーを作る DAY1

Last updated at Posted at 2024-11-21

はじめに

Qiitaに投稿するのはこれが初めてになります。

投稿ネタとして、Pythonを使ったオーディオプレイヤ―の実装過程を勉強がでら順次投稿する予定です。オーディオプレイヤーを選択した理由は2つあります。以前、英語の勉強のためにオーディオファイルを任意の場所で分割するコードを実装したことがあります。音声データの取り扱いについて多少理解をしているものの、あいまいな部分も多く、いつか時間が取れた時に確認したいと思ってました。これが1つ目の理由です。また、Pythonを使ったGUIの実装に不慣れだというのが2つ目の理由です。

なお、ソースコードはGitHubに公開する予定です。

GUIライブラリ

今回は、CustomTkinerを使用します。Pythonに標準的に組み込まれているTkinterをベースに開発されたモダンなデザインのGUIライブラリです。TkinterもモダンなデザインのGUIを実装可能ですが、コストをかけずに洗練されたデザインのGUIを実装できるところがCustomTkinerの良いポイントだと思います。また、Tkinterをベースにしているため、Tkinterを使った経験がそのまま活用できますし、CustomTkinerについてわからないところもTkinterで検索すれば解決策がすぐに見つかるところも良いです。

今回実装した内容

  • GUI
    • CTkFrameで3つのサブフレーム
      • CoverArtDisplayFrame
        • タイトルやアルバム名などのメタ情報やカバーアートを表示する領域
      • AudioPlayerControllerFrame
        • プレイヤーをコントロールするボタンなどを配置する領域
      • FileDialogFrame
        • ファイルダイアログを配置する領域
    • CTkButtonで再生やポーズなどを行うためのボタン
    • CTkLabelでメタ情報を表示するラベル
    • CTkEntryでファイルパスを表示するテキストエントリ
  • 機能
    • WAVファイルの入力と再生

widgets.jpg

ソースコード

環境

Windows11
Python3.12.7

ソースコードの補足

アルバムアートの表示

simple_audio_player_ver0.1.py
    def set_cover_art(self):
        if self._default_light_image == None and self._default_dark_image == None:
            None
        else:
            self._cover_art_display_label.configure(image=customtkinter.CTkImage(light_image=self._default_light_image, dark_image=self._default_dark_image, size=(250, 300)))
            self._cover_art_display_label.configure(text='')

カバーアートを含まないオーディオファイルに対して、代替画像を表示するコードです。
CTkLabelで作成したラベルself._cover_art_display_labelに、画像customtkinter.CTkImage(...)を表示しています。画像を表示するときは、CTkLabelではなくtkinter.Canvasあるいはcutomtkinter.CTkCanvasに表示することが多いと思いますが、今回はこのようにしました。cutomtkinterは外観に関してダークモードとライトモードの切り替えが容易であり、CTkImageを使えばそれぞれのモードに対応した画像も自動的に切り替えてくれるからです。

オーディオの再生

simple_audio_player_ver0.1.py
    def __play(self):
        try:
            p = pyaudio.PyAudio()
            stream = p.open(format=p.get_format_from_width(self._audio.samplewidth), channels=self._audio.nchannels, rate=self._audio.framerate, output=True)
            self._audio.rewind()
            while True:
                data = self._audio.read_frames(CHUNK_SIZE)
                if len(data) == 0:
                    self._audio.rewind()
                    break
                stream.write(data)

            p.terminate()
        except:
            print('Error: cannot play the audio')
            return

オーディオファイルの再生に、PyAudioを使用しました。
再生中にイベントループが停止するため、上記の部分をマルチスレッド処理あるいはマルチプロセス処理させる必要があります。

ファイルダイアログ

simple_audio_player_ver0.1.py
input_path = customtkinter.filedialog.askopenfilename(filetypes=[('Audio File', '*.wav')])

customtkinterの部分をtkとしても全く同じです。同様の関数を使用すれば、フォルダ単位の入力や複数のファイル入力にも対応できます。

備考

  • オーディオファイルはWAVだけに対応
    • MP3などの他のフォーマットは次回以降に対応
  • WAVのメタ情報にそもそもカバーアートが含まれていないため、代替画像を表示
    • プレイヤー側が対応していれば、アートワークを含めることも可能
  • WAVのメタ情報を簡単に取得するPythonパッケージがなかったため、代替テキストを表示
    • mutagenでは取得できなかった
    • tinytagでは取得できたが、日本語で文字化けが発生した
    • 時間があればWAVのメタ情報をパースするPythonコードを実装する
  • threadingあるいはmultiprocessingを使用していないため、オーディオを再生中にイベントループの処理が止まる。つまり、ポーズボタンを押しても停止させることができず、再生ボタン以外は意味をなしていない。強制終了以外に、アプリを終了すことすらできない
    • 次回以降に対応

おわりに

次回に続きます。
Qiitaの作法やソースコードに関するアドバイス、質問などがありましたらコメント欄までお願いします。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?