pythonで音付き動画再生!!(tkinter/imageio)

# 0.記事の編集予定について
この記事の内容を執筆時から長い間研究?していたため、その結果を使ってもう一度書き直します。   
公開まで今しばらくお待ちください....

# 1.動作確認環境
python3(python2での動作は未確認です。 2020/5/21追記)
linux(debian 64bit)
cpu: intel core i7
# 2.この記事のコード実行に必要なもの

~~5/20追記:
pip3 install tkmedia
とffmpegとportaudioのインストール(5/30追記)でインストールできるようになりました。~~
(翌年1月21追記)困ったことに、コードがおかしなことになっていて、pipの方が使えなさそうです。
修復しますので、今しばらくお待ちを...
あと、portaudioは、linuxを除き、sounddeviceにくっついてくるので、win and mac ではインストール要りません。
間違えた情報を投稿してしまい、すみませんでした。
linux(debian(ubuntu))ではlibportaudio0って名前で配布されてます。
sudo apt inatall libportaudio0で入ります。
### python モジュール
 tkinter(pythonに付属,linuxの場合python3-tkをインストール
 imageio
 pillow
 pydub
 sounddevice
 numpy
 あと、いくつかのpython付属モジュール
 (全て"pip3 install [モジュール名]"でインストールできます。)
### その他ソフトウェア
 ffmpeg

(あと、そこそこの性能のPC(iから始まる会社の鉄腕OOOやセレOOは非推奨)

# 3.この記事のコードの利用方法
次のようにファイルを配置する。

```
media
|-__init__.py(空)
|-audio.py
|-video.py
```

"play_video" があるディレクトリで次を実行:

```python
import media.video
import media.audio
import tkinter

video = video.Video()
audio = audio.Audio()

root = tkinter.Tk()
root.frame = tkinter.Label(root)
root.frame.pack()

video.openfile("[videofile_path]")
audio.openfile("[videofile_path]")

audio.play()
video.play()
root.mainloop()
```

ライセンスはフリーにしておきます。
編集、再配布、営利目的での使用等どうぞご自由に。(使う人がいるのか知りませんが)
できれば、コメントかメール(marusoftware@outlook.jp)にご一報いただけると幸いです。
あと、使用しているライブラリとソフトウェアのライセンスはよーくご確認ください。
ただし、筆者は一切責任を取りませんのでよろしくおねがいします。

# 4.音声の再生
pydubとsounddevice(とnumpy)を使う

```python:audio.py
import sounddevice
import pydub
import time
import numpy

class Audio():
    def __init__(self):
        pass
    def openfile(self, filepath):
        if ".mp3" in filepath:
            self.segment = pydub.AudioSegment.from_file(filepath,codec="mp3")
        elif ".wav" in filepath:
            self.segment = pydub.AudioSegment.from_file(filepath,codec="wav")
        elif ".mp4" in filepath:
            self.segment = pydub.AudioSegment.from_file(filepath)
        else:
            self.segment = pydub.AudioSegment.from_file(filepath)
    def play(self, place=0):
        if self.segment.channels != 1:
            self.samples = numpy.array(self.segment.get_array_of_samples().tolist(),dtype="int16").reshape(-1,self.segment.channels)
        else:
            self.samples = numpy.array(self.segment.get_array_of_samples().tolist(),dtype='int16')
        sounddevice.play(self.samples,self.segment.frame_rate)
    def stop(self):
        sounddevice.stop()
```

# 5.映像の再生
imageio で読み込んでPILを介してtkinterのフレームに表示する(っていうのをマルチスレッドで行う)。

```python:video.py
import tkinter
from tkinter import ttk
import imageio
from PIL import ImageTk, Image
import time
import threading
from imageio.plugins.ffmpeg import FfmpegFormat

class Video():
    def __init__(self):
        format = FfmpegFormat(
            "ffmpeg",
            "Many video formats and cameras (via ffmpeg)",
            ".mov .avi .mpg .mpeg .mp4 .mkv .wmv .webm",
            "I",
            )
        imageio.formats.add_format(format,True)#webm に対応させる。(だいぶ強引に)
    def openfile(self, file_path,frame):
        self.frame = frame
        try:
            self.video = imageio.get_reader(file_path)
        except imageio.core.fetching.NeedDownloadError:
            imageio.plugins.avbin.download()
            self.video = imageio.get_reader(file_path)
    def play(self):
        self.video_thread = threading.Thread(target=self._stream)
        self.video_thread.start()
    def stop(self):
        self.video_thread.stop()
    def _stream(self):
        start_time=time.time()
        sleeptime = 1/self.video.get_meta_data()["fps"]
        frame_now = 0
        for image in self.video.iter_data():
            frame_now = frame_now + 1
            if frame_now*sleeptime >= time.time()-start_time:
                frame_image = ImageTk.PhotoImage(Image.fromarray(image))
                self.frame.config(image=frame_image)
                self.frame.image = frame_image
                time.sleep(sleeptime)
            else:
                pass
```