前提・実現したいこと
PySimpleGUIでマルチスレッドを使い、音楽波形を表示しつつPNG画像を切り替え表示したい。
下記参照URLを利用し作成しました。
参照
①PySimpleGUIを使用したmultithreadの作り方
②スペクトラム波形作成
③
著作権フリーwavは、こちらのwavを使っています。
④ボタンによる画像選択表示の参照コードサンプル
該当のソースコード
#------------myimgviewer.py------------------------- import PySimpleGUI as sg from PIL import Image, ImageTk import io import sys import pyautogui import time # import os flag_status = 0 img_types = (".png", ".jpg", "jpeg", ".tiff", ".bmp") status_text = '' fname = None if not len(sys.argv) == 1: fname = sys.argv[1] # print('a') # print(fname) if fname: if not fname.lower().endswith(img_types): sg.popup("Cancel", fname + " is Not image file") raise SystemExit("Canceling: Not image file") def get_img_data(f, maxsize=(1200, 850), first=False): """Generate image data using PIL.""" global status_text im = Image.open(f) status_text = "%d x %d" % im.size # original image size im.thumbnail(maxsize) status_text += " (%d x %d)" % im.size # thumbnail image size if first: bio = io.BytesIO() im.save(bio, format="PNG") del im return bio.getvalue() return ImageTk.PhotoImage(im) # layout firstfg = True if fname: print('b') image_elem = sg.Image(data=get_img_data(fname, first=firstfg)) fname_elem = sg.InputText(size=(80, 1), enable_events=True, default_text=fname) firstfg = False else: print('c') image_elem = sg.Image() fname_elem = sg.InputText(size=(80, 1), enable_events=True) status_elem = sg.Text(key='-STATUS-', size=(64, 1)) #-------------myimgviewer.py--------------------------------------------- #-------------wave_spect.py---------------------------------------- #!/usr/bin/env python3 import wave import sys import pygame from pygame.locals import * import scipy.fftpack as spfft import soundfile as sf import pyaudio import numpy as np import pdb # -------------------------------------------------------------------- # パラメータ # -------------------- fn = "futta-fly.wav" # 計算用 CHUNK = 1024 # pyaudioでストリームにチャンク単位で出力(何故1024かはよく知らない) start = 0 # サンプリング開始位置 N = 1024 # FFTのサンプル数 SHIFT = 1024 # 窓関数をずらすサンプル数 hammingWindow = np.hamming(N) # 窓関数 t = 0 # -------------------- # 描画用 SCREEN_SIZE = (854, 480) # ディスプレイのサイズ rectangle_list = [] # -------------------- # pygame画面初期設定 pygame.init() screen = pygame.display.set_mode(SCREEN_SIZE) pygame.display.set_caption("Pygame Audio Visualizer") #-------------wave_spect.py---------------------------------------- from queue import Queue from threading import Thread import time import PySimpleGUI as sg ui_que = Queue() data_que = Queue() def show_time(): jikan = time.strftime('%p %I:%M:%S') return jikan def worker(data_que,ui_que): result = data_que.get() time.sleep(1) ui_que.put(result[0]) def worker_do(data_que,ui_que): result_do = data_que.get() time.sleep(0.5) ui_que.put(len(result_do)) sg.theme('Dark') layout = [ [fname_elem, sg.FileBrowse(key='-FILENAME-')], [status_elem], [sg.Text(size=(15, 1), font=('Helvetica', 20),justification='center', key='-jikan-')], [sg.Text('Data', size=(8, 1)),sg.Input(key='-data-')], [sg.Submit(), sg.Button('DO',key='-do-'), sg.Cancel()], [image_elem], [sg.Text('Data2', size=(8, 1)),sg.Input(key='-data2-')], [sg.Text('Status:', size=(8, 1)),sg.Text('',size=(8,1), key='-status-')], [sg.Output(size=(70, 2))] ] window = sg.Window('template',layout) # 「FFTかけて描画」を繰り返す. def redraw(): global start global screen global rectangle_list # -------------------- # 対象サンプル点のブロックにFFTをかけて振幅スペクトルを計算する windowedData = hammingWindow * x[start:start + N] # 窓関数をかけたデータブロック X = spfft.fft(windowedData) # FFT amplitudeSpectrum = [np.sqrt(c.real ** 2 + c.imag ** 2) for c in X] # 振幅スペクトル # -------------------- # Pygameでの描画 screen.fill((240, 128, 128)) # 好きな色で初期化する rectangle_list.clear() # 四角形リスト初期化 # スペクトル描画 数値は実行して調整しながら for i in range(86): rectangle_list.append(pygame.draw.line(screen, (102, 205, 170), (1+i * 10, 350 + amplitudeSpectrum[i].astype(int) * 10), (1+i * 10, 350 - amplitudeSpectrum[i].astype(int) * 10), 4)) # ディスプレイ更新 pygame.display.flip() start += SHIFT # 窓関数をかける範囲をずらす if start + N > nframes: sys.exit() for event in pygame.event.get(): # 終了処理 if event.type == QUIT: sys.exit() if event.type == KEYDOWN: if event.key == K_ESCAPE: sys.exit() # wavデータを取得 data, fs = sf.read(fn) # dataの形状は(フレーム数×チャンネル数) if data.ndim == 1: x = data # モノラルならそのまま使う if data.ndim == 2: x = data[:, 0] # ステレオならLチャンネルだけに絞って処理することに(Rなら0を1にしてください) nframes = x.size # フレーム数取得(FFTで窓関数をずらすときの終了条件に使います) # -------------------- # 再生と描画を開始 filename = fn wf = wave.open(filename, "r") # ストリームを開く p = pyaudio.PyAudio() stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), channels=wf.getnchannels(), rate=wf.getframerate(), output=True) # 音声を再生 data = wf.readframes(CHUNK) # while data != '': #--------wave_spect.py------------------------------- while True: # wave_spect.py---------α stream.write(data) data = wf.readframes(CHUNK) redraw() # wave_spect.py--------- event, values = window.read(timeout=100,timeout_key='-timeout-') # event, values = window.read() #timeoutを指定することで、timeoutイベントが起こります。timeoutの単位ms # print(event,values) #↑コメントアウトを外すと、どんなイベントが起こっているか確かめることができます。 if event in (None,'Cancel'): #--------myimgviewer.py------------------------------ fname = 'C:\work\music\f3.png' image_elem.update(data=get_img_data(fname, first=firstfg)) print(fname) fname_elem.update(fname) status_elem.update(status_text) firstfg = False #--------myimgviewer.py------------------------------ elif event in 'Submit': print('Input data: {}'.format(values['-data-'])) # デーモンスレッドはデーモンスレッド以外のスレッドがなくなった時点で全て自動終了する thread = Thread(target=worker, args=(data_que,ui_que), daemon=True).start() data_que.put(values['-data-']) #--------myimgviewer.py------------------------------ fname = 'C:\work\music\f1.png' image_elem.update(data=get_img_data(fname, first=firstfg)) print(fname) fname_elem.update(fname) status_elem.update(status_text) firstfg = False #--------myimgviewer.py------------------------------ elif event in '-do-': print('do somthing: {}'.format(values['-data2-'])) thread_do = Thread(target=worker_do, args=(data_que,ui_que), daemon=True).start() data_que.put(values['-data2-']) #--------myimgviewer.py------------------------------ fname = 'C:\work\music\f2.png' image_elem.update(data=get_img_data(fname, first=firstfg)) print(fname) fname_elem.update(fname) status_elem.update(status_text) firstfg = False #--------myimgviewer.py------------------------------ elif event in '-timeout-': jikan = show_time() window['-jikan-'].update(jikan) try: ui_data=ui_que.get_nowait() except : ui_data = None if ui_data: print(ui_data) window['-status-'].update(ui_data) stream.close() p.terminate() window.close()
発生している問題・エラーメッセージ
コードのαと記載した3行を今の配置した場所ですと、音楽がカクカクに再生されてしまっています。
Cancel(名前変えずおかしくてすみません。)イベントに移しても再生されず、音楽再生と画像ボタン表示が同時に実行できませんでした。
試したこと
αの場所を変えることは効果がありませんでした。
本来の目的は波形を同じ画面内に小さくして表示したいのですが、それはいったんおいておき、同時に再生と表示をしようとwhileを別にするなど試しましたが、そもそも片方だけループしてしまいだめでした。
どのようにすれば同時実行が可能になるでしょうか。アドバイスどなたか頂けないでしょうか。
よろしくお願いいたします。
補足情報(FW/ツールのバージョンなど)
windows10
PySimpleGUI
Python3.8.8
アドバイス直後に実装したコード①
先ほどこちらにコード記載したところ18000文字となってしまい8000文字オーバーになってしまいました。
コメントに記載してみます。
multiprocessingが簡易なのではとコーディングしましたが、音楽は流れて波形も表示されていますが、画像表示のボタンを押した際にフリーズになってしまいました。
アドバイス頂いた直後に実践したコード
python
1# 先頭に 2# ファイルの先頭で 3import logging 4logger = logging.getLogger(__name__) 5 6# ログにスレッド名を表示&ロギングを有効にする設定 7# if __name__ == "__main__": 内に置きたいが、なければファイル先頭でもok 8logging.basicConfig(level=logging.DEBUG, format="[%(threadName)-10s][%(levelname)-8s] %(message)s") 9 10# logger モジュールは、スレッドセーフなので、どこからでも安全に呼び出せます。 11# print の代わりに利用 logger.info, logger.debug, logger.error, 等 12fn = "futta-fly.wav" 13logger.debug(f"debug message: {fn=}") # f-string で変数 fn を表示 14 15#~略~ 16 17def play_audio(): 18 # TODO: リソース作成・解放はスレッド側で完結する 19 wf = wave.open(filename, "r") 20 # ストリームを開く 21 p = pyaudio.PyAudio() 22 stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), 23 channels=wf.getnchannels(), 24 rate=wf.getframerate(), 25 output=True) 26 27 # 音声を再生 28 data = wf.readframes(CHUNK) 29 while True: 30 stream.write(data) 31 redraw() 32 33 elif event in '-do-': 34 # ボタンを押した時に開始 35 thread = threading.Thread(target=play_audio, args=(data_que,ui_que), daemon=True) 36 thread.start()
回答2件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/06/26 11:00
2021/06/26 12:10
2021/06/26 14:29
2021/06/26 15:05 編集
2021/06/26 16:15
2021/06/26 16:35
2021/06/27 00:41 編集
2021/06/27 05:17
2021/06/29 08:45
2021/07/17 12:29