🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

3082閲覧

PyAVで、動画と音声(と字幕)のストリームを一つで完結させたい。

Marusoftware

総合スコア189

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

1クリップ

投稿2021/03/07 05:45

PyAVを使って動画を音声(と字幕)をTkinterのウィンドウ内に再生するライブラリを開発しています。
(宣伝になってしまったらすみませんが、以下にGithubのリンクを貼っておきます。)
Marusoftware/tkmedia3

で、問題は何かというと、
1つ目は、

python3

1import av 2 3con = av.open("/path/to/video") 4a = con.streams.audio[0].decode() 5v = con.streams.video[0].decode()

として読めないことです。
なぜか、空のリストが出てきたりエラーになったりします。

仕方がないので、例にもあるようにすると、

python3

1import av 2 3con = av.open("/path/to/video") 4a = con.decode(audio=0) 5v = con.decode(video=0) 6 7print(next(a).index) 8print(next(v).index)

として読むと、位置がおかしなことになることです。
最初のprintで出力されるaudioの位置と
二個目のprintで出力されるvideoの位置は
同じなのですが、実際に出てくるのは
0番目のaudioと1番目?のvideoです。
マルチストリームにしなければいけないんでしょうか??

表題に書かれた問題はこれだけなのですが、
pyavでlibavfilterのフィルター(主にpad)を使いたいのですが、
それも教えていただけると幸いです。

よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

t_obara

2021/03/07 10:29

位置がおかしなことになるとは、具体的にどのようになるのでしょうか?おかしいと判断した理由はなんですか?
Marusoftware

2021/03/08 00:04 編集

リンク先にあるライブラリの開発時に発生したものなのですが、一つのコンテナ?から読むと、映像・音声共に倍速?で再生されてしまうという問題が発生しました。 フレームについてくるindexは正常な値(ただし、実時間とは合わない)を示していますが...
t_obara

2021/03/08 05:15

ptsは取得できているのですか?取得できているのであれば、再生の時にはそれを参照すべきかと
Marusoftware

2021/03/08 08:41

ありがとうございます... やってみます。 ptsって再生されるべき時間であってますか?経過時間ですか?それとも、長さですか?
t_obara

2021/03/08 09:27

そのパケットが再生されるべき時間ですね。dtsがデコードされるべき時間
Marusoftware

2021/03/08 09:33

あ、そういうことだったんですね! ありがとうございます!
guest

回答1

0

ベストアンサー

python

1con = av.open("/path/to/video") 2a = con.streams.audio[0].decode() 3v = con.streams.video[0].decode()

audio, video は tuple の様なので、
どのようなストリームがあるかは、ファイル次第になるはずです。

ストリームを一緒に扱うという意味では

python

1for frame in con.decode(audio=0, video=0): 2 ...

但し、audio[0], video[0] が失敗する場合は、同様にエラーになるはずです。
→ 読み込むファイルの形式を確認

decode() はジェネレーターなので、コンテナ内でファイルポインタを共有してる場合、
個別に decode を呼び出すのは、読み飛ばしが発生してそうですね。
複数のストリームを対象にした読み出しは demux() を用いてます。
(decode() 内部でも呼び出される)

python

1a = con.stream.get(audio=0) 2v = con.stream.get(video=0) 3for packet in con.demux(a, v): 4 # NOTE: packet.stream.type: "audio", "video", ... 5 6 if packet.dts is None: 7 continue 8 9 for frame in packet.decode(): 10 ... 11 12# or 13 14for packet in con.demux(audio=0, video=0): 15 ...

複数のフレームが混同したストリームを、それぞれ個別に振り分けて処理したい場合、
双方向ジェネレーターを使った例の紹介。

※ PyAV に関しては全く知識はありません。audio系は扱ったことがありますが、videoは全然。
使い方は把握してないので、フレーム読み込みでロスがないかの確認程度。

python

1from itertools import groupby 2 3import av 4from av.audio.frame import AudioFrame 5from av.video.frame import VideoFrame 6 7con = av.open("/path/to/video") 8 9def limit(count, stream): # テスト確認用 10 for _, item in zip(range(count), stream): 11 yield item 12 13def process_audio_frames(): 14 while 1: 15 frame = yield 16 print("audio", frame) 17 18def process_video_frames(): 19 while 1: 20 frame = yield 21 print("video", frame) 22 23audio = process_audio_frames() 24next(audio) # ※ send() を使うため、yieldまで進める 25video = process_video_frames() 26next(video) 27 28for frame_type, group in limit(3, groupby(con.decode(audio=0, video=0), key=type)): 29 if frame_type is AudioFrame: 30 for frame in group: 31 audio.send(frame) 32 elif frame_type is VideoFrame: 33 for frame in group: 34 video.send(frame)

代替は、スレッドで処理するなら Queue にしたり、asyncio なら janus(sync <=> async Queue)


PyAV #239 -- any exmple how to use filter?

audio,video ストリームの所得

python

1 ivstrm = next(s for s in icntnr.streams if s.type == b'video') # input-video-stream 2 iastrm = next(s for s in icntnr.streams if s.type == b'audio') 3 4# 辞書順が保証される python3.7 以降なら 5# ライブラリのコードとしては、おすすめできませんが 6 7 ivstrm, iastrm = icntnr.streams.get(video=0, audio=0) 8 9 assert isinstance(iastrm, av.AudioStream) # nox等で3.6以前をテストする際に 10 assert isinstance(ivstrm, av.VideoStream) # 誤コードがテストをpassしないように確認 11 12# python3.6 13 ivstrm, iastrm = icntnr.streams.get(OrderedDict([("video", 0), ("audio", 0)]))
  • Graph/Filter で filter chain を構成して
  • フレーム(ifr)を末端へ push
  • フレーム(ofr)を取り出し pull

python

1# 各 Graph のノードを link_to で繋ぐユーティリティ関数 2# issue 内コメントから借用 + 戻り値を追加 3def link_nodes(*nodes): 4 for c, n in zip(nodes, nodes[1:]): 5 c.link_to(n) 6 return nodes 7 8# フィルタ準備 9graph = Graph() 10nodes = link_nodes( 11 graph.add_buffer(template=ivstrm), 12 graph.add("scale", "iw/2:ih/2"), 13 graph.add('buffersink') 14) 15graph.configure() 16 17# フィルタ適用 frame を push/pull 18# ([a]buffer/[a]buffersink がある場合は、Graphのpush/pull内で自動判別) 19graph.push(inframe) 20outframe = graph.pull() 21 22# ない場合は、自分で明示的に、先端のbufferへpush, 末端のfilterから pull 23nodes[0].push(inframe) 24outframe = nodes[-1].pull()

投稿2021/03/08 04:55

編集2021/03/08 06:13
teamikl

総合スコア8742

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Marusoftware

2021/03/08 08:47 編集

a = con.streams.audio[0].decode() これなんですが、困ったことに空のリストが出てくるんですよね...audioも同様に なんというか、長さになんの問題もないんですがね...そして、何回も繰り返すと、RangeErrorだったかな?が出るんですよね... そんなことから、2つ目の方法に移ったわけですが、今度は、異常に早く再生が終わってしまうんですよね..... おそらく、ジェネレーターが内部的に共有されているというのは正しいようです... なので、質問前には、マルチストリームでやってみたんですよね。まあ、当然うまく行くんですが。ただ、httpでストリーミングさせようとすると、2つもストリーム開いてるのもあって、なんかアクセスログがえらいことになるんですよね... まあ、そんなこともあって質問させて頂いたのですが、demux...これって、decodeと何が違うのか結局わからなかったんですよね。教えていただけると幸いです... それはさておき、一度demuxで試してみたいと思います。(使い方を今から考えに行くんですがね...) できれば、for文を使わないでできるといいのですが... 最後になりましたが、filterの方本当にありがとうございます.....丁寧にコメントまでつけていただいて.... そこまでしてもらっても、buffersinkのとことかで、躓いてしまったのですがね...
teamikl

2021/03/08 09:49 編集

マルチストリームってそういう意味だったのか。レポジトリ内の test.py で把握しました。 ---- 実装だけ見ると、demux は パケット単位での読み出し、decode はフレーム単位でした。 demux 内部は、パケットを構築して、データを読み出し、 そのパケットの ストリームの情報が、読出対象のストリームならば yield するジェネレーター。 decode() の実装は、packet から frame を取り出すだけのシンプルなwrapper っぽいです。 3行程のコード https://github.com/PyAV-Org/PyAV/blob/60cbc412b7c9fea7bac624ec06b168b58ff785a1/av/container/input.pyx#L169 使い分け指針としては、 - packet の情報が必要な場合は demux (参考: demux() の doc-string) - frame操作のみならばdecode (decode 内部で demux が呼び出されている) >for文を使わないでできるといいのですが... これは、GUIのイベントループとの兼ね合いですか? ここは素直にマルチスレッド or マルチプロセスを検討が良いと思います。
Marusoftware

2021/03/08 10:20

>マルチストリームってそういう意味だったのか。レポジトリ内の test.py で把握しました。 ...説明不足で本当にすみません。 そうなんですね!demuxはPacketを返す感じですね! (間違ってたらすみません...) ただ、困ったことに、 con.decode(audio=0, video=0)ってすると、なぜか片方しか返ってこないのですが... 何なんでしょうね........
Marusoftware

2021/03/08 10:21

> これは、GUIのイベントループとの兼ね合いですか? > ここは素直にマルチスレッド or マルチプロセスを検討が良いと思います。 一応、threadingをaudioの方では使っているのですが、 videoでは、threading使うと、tkinterのlabelへの描画が追いつかないのか、ちらつくんですよね... そんなこともあって、afterを使っているのですが、どうなんでしょう......
teamikl

2021/03/08 11:37

> con.decode(audio=0, video=0)ってすると、なぜか片方しか返ってこないのですが... con.streams.get(audio=0, video=0) とするとどうですか? ストリーム自体の有無と、読み込まれるframeの区別が必要で、 ストリームがaudio/videoどちらか片方しかない場合は、 読み込む前にエラーになるはずです。 IndexError: tuple index out of range ---- 描画が追い付かない問題は、tkinter の不利な部分ですね。 tkinter 自身 thread で tcl/tk と文字列で通信しているし、tkinterでは プラットフォーム依存な方法以外で、高速な描画手段は思いつかない。 読み出し~(Tk以外の)Imageデータ構築迄だけでも、外部スレッドにする利はありますが、 フレーム毎にオブジェクトを作ったりしてる時点で…という気はします。
teamikl

2021/03/08 12:48

tkinter での blit は PIL.ImageTk が担当してたのですね。 訂正・補足で、画像データ自体は PILのC拡張部分が PyImagingPhoto というTCLコマンドを登録して python -> tcl/tk へは、メモリ上のアドレス値のみを文字列で伝えている。 (PhotoImage(data=...) みたいに、文字列内に画像データが乗っかるわけではない) 思いついた方法メモ、先読み pre-fetch くらいかな。 データを読み込みながら after で直ぐに描画ではなく、 予め一定量を読み込んだうえで、描画フレームの FPS に合わせてタイマーの間隔を調整 イベントループ側で行う処理は、tk絡みの部分・描画内容の更新のみで良い。 オブジェクトを無駄に作らないように、バッファを有効活用する。(リングバッファ)
Marusoftware

2021/03/08 16:26

> con.streams.get(audio=0, video=0) とするとどうですか? できました!! ありがとうございます! > ストリーム自体の有無と、読み込まれるframeの区別が必要 それもある程度できているので、可能かと思います。 > 描画が追い付かない問題は、tkinter の不利な部分ですね。 そうですね...cpythonは、なんだか、いろいろ経由しているのもあって、遅いですね... pypyはlibtk&tclを直で呼ぶのもあって早かったりしますが...どうにかして、cpythonに実装できないものですかね... > データを読み込みながら after で直ぐに描画ではなく、 > 予め一定量を読み込んだうえで、描画フレームの FPS に合わせてタイマーの間隔を調整 えっと、こちらで考えているのは、読んだPILのImageをqueueにためておいて(貯める作業は、別スレッド)、描写は、afterですかね... なんていうか、例のライブラリの音声の方は、そのような仕組みになってたりするんですがね... いろいろありがとうございます。 頑張ってみます。
teamikl

2021/03/09 04:29 編集

メインスレッドでフィルターを使ったりして処理が追い付かないようなのを考えてましたが、 その場合は、症状としては画像は途切れたりしないはずなので、(再生速度が遅くなるはず) ちらつきの原因は、描画とは別のタイミングで image が破棄されてるケースも考えられます。 事例: PhotoImage作成と描画更新(configure image)を別のタイミングにすると、 ちらつきが発生しました ※ PhotoImage オブジェクトのデストラクタで破棄されるので、GCのタイミング ---- ジェネレータの扱いの問題 - VVA - VAV の順にそれぞれ読み込んだ場合、(A: audio, V: video) 読み込みのロスを確認出来ました。 対象のファイル次第だと思いますが、以下のような感じです - [V][V]VVVV[A]AAAAAVVVVVAAAAAA - [V]VVVVV[A]AAAAA[V]VVVVAAAAAA ---- > 読んだPILのImageをqueueにためておいて(貯める作業は、別スレッド) 概ね同じ考えです。 > 、描写は、afterですかね... 描画をtkinterのイベントループのスレッドで行うのは必須事項なので、無難な方法だと思います。 一応、その前の「キューの通知」という観点では、幾つか選択肢がありますが、 導入が大変だったり事例が少ない為、わざわざ検討する程の強い理由はない、といった程度です。 (他: asyncio 非同期queue, パイプ&監視で通知、mtkinterでイベント通知)
Marusoftware

2021/03/16 15:22

度々すみません.. あのあと、con.streams.get(audio=0, video=0)でやってみたのですが、出てくるストリーム内のdecodeを使ってもうまいことフレームが出てきません... どうかご教授よろしくおねがいします...
Marusoftware

2021/03/16 15:28

失礼しました! demux or decodeがいるようですね... もっかいがんばります...
teamikl

2021/03/16 16:25

ストリームからの読み出しを、audio/video片方ずつ個別に読み出したりしてませんか? 読み出しは同時にして、個別の処理に振り分ける必要があります。 audio/videoを交互に個別に読みだした場合は、 上のコメントに書いた「ジェネレータの扱いの問題」に記載した状況になりました。 回答に、audio/video を処理する関数(双方向のジェネレーター)に振り分ける方法のコードを掲載してるので、 まずは、パケット単位・フレーム単位でログを出力して、 データ読み出しの欠損がないかを確認してみてはどうでしょう。 ---- あと、別の問題だと思いますが、現象が「ちらつき」であれば、 処理速度等ではなくPhotoImage 破棄のタイミングなので改善可能な問題かもしれません。 gifのアニメーション実装で同様の現象になって修正したことがあります。 queue をmaxsize指定なしに使っていると、 再生速度に比べてデータ読み出しが先に進み過ぎ、queueにデータを貯めすぎて 動作が重くなることもあるので、状況ややりたいことの実装次第ですが CPythonでも改良の余地はあると思います。
Marusoftware

2021/03/21 02:01

度々すみません(これも何回書いたのだろう...) ちょっと「あれ?」と思うことがあったので確認します。 import av ffmpeg=av.open("/path/to/video") a = ffmpeg.streams.get(audio=0) v = ffmpeg.streams.get(video=0) for packet in ffmpeg.demux(a, v): for frame in packet.demux(): print(type(frame)) ってすると、 <class 'av.audio.frame.AudioFrame'> <class 'av.video.frame.VideoFrame'> <class 'av.video.frame.VideoFrame'> <class 'av.video.frame.VideoFrame'> <class 'av.video.frame.VideoFrame'> <class 'av.video.frame.VideoFrame'> <class 'av.audio.frame.AudioFrame'> <class 'av.audio.frame.AudioFrame'> <class 'av.audio.frame.AudioFrame'> <class 'av.audio.frame.AudioFrame'> <class 'av.audio.frame.AudioFrame'> <class 'av.audio.frame.AudioFrame'> <class 'av.audio.frame.AudioFrame'> <class 'av.audio.frame.AudioFrame'> <class 'av.audio.frame.AudioFrame'> <class 'av.audio.frame.AudioFrame'> <class 'av.audio.frame.AudioFrame'> <class 'av.audio.frame.AudioFrame'> <class 'av.audio.frame.AudioFrame'> のように出力されます。(長くてすみません......) これの順番って、一体どのように決まるのでしょう...?
Marusoftware

2021/03/21 03:02

>読み出しは同時にして、個別の処理に振り分ける必要があります。 やってみました!うまいことできました!ありがとうございます! >queue をmaxsize指定なしに使っていると、 ただ、maxsize指定すると、ブロックされたときものすごく致命的なんですよね... なので、(というか上のようにフレームが交互に出てこないこともあって)ボーダー値を設定して、そこまで読んで、取り出しまで待つというような仕組みにしてみました。 (コードはgithubに公開しました。) >処理速度等ではなくPhotoImage 破棄のタイミングなので改善可能な問題かもしれません。 破棄のタイミングって、tkinterのimageをconfigureで更新したあとで大丈夫ですか?
teamikl

2021/03/21 05:51

パケットやフレームの順序は、(条件絞り込みの)フィルターに掛けているようでした。 ストリームから順番に読み出し、パケットがdemux や decode の引数に指定した対象のモノなら、 値を返すような感じ。指定しなかったものは読み飛ばされます。 データの格納順序次第だと思いますが、 パケット単位では AVAVAV と交互で、フレーム単位では AAAAVVVVAAAA 交互に連続 という感じでした(ファイルフォーマットの仕様は知りません。手元のファイル数件でテストのみ) > ただ、maxsize指定すると、ブロックされたときものすごく致命的なんですよね... Audio と Video が有るのを忘れてました。ブロック自体は対策可能ですが Audio は読み出す必要があるのに、Video の queue でロックという状況が起こると面倒ですね。 キューのサイズは、余裕をもたせる必要があります。 ブロック自体の対処方法は、 A queue.put に timeout 指定 B queue に put している producer に停止要求。 (この時点ではブロック中なので止まらない)  consumer 側で queue を読み出してブロック解除。 ここは maxsize を指定した上で、ブロックしないように運用するのが正着でしょうか。 ブロックによるタイミング制御だけでは、少し手抜き的なところはありますが、 読み出しタイミング制御部分にバグが有った場合でも、 queue 起因でパフォーマンスが低下する問題を確実に防げる利点があります。 >>処理速度等ではなくPhotoImage 破棄のタイミングなので改善可能な問題かもしれません。 >破棄のタイミングって、tkinterのimageをconfigureで更新したあとで大丈夫ですか? そうですね。configure で指定されてる間は残す必要があります。 実際のコードとの間に齟齬が無ければ、ちらつきにはならないはずですが、 (※ imageの描画領域全体が一瞬ホワイトアウトするような現象を想定) GC のタイミングと、Tcl/Tk の描画更新のタイミングとの兼ね合いもあるので コード上の配置とは一致しない可能性もあります。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問