メインコンテンツへスキップ
見出し画像

FFMPEGで標準出力を使うとPythonのwaveでエラーになった話(解決策有り) ~出力方法が違うとデータの一部が不正な状態になる~

    AutoVoxDub.py", line 560, in SplitFile
        ww.writeframes(frames)
      File "C:\py\python3116\Lib\wave.py", line 558, in writeframes
        self.writeframesraw(data)
      File "C:\py\python3116\Lib\wave.py", line 547, in writeframesraw
        self._ensure_header_written(len(data))
      File "C:\py\python3116\Lib\wave.py", line 588, in _ensure_header_written
        self._write_header(datasize)
      File "C:\py\python3116\Lib\wave.py", line 600, in _write_header
        self._file.write(struct.pack('<L4s4sLHHLLHH4s',
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    こんにちはRcatです。
    いきなりですが、上のエラーをご確認ください。
    こちらはPythonのwaveライブラリを使ってファイルを分割しようとした時に出たエラーです。
    メッセージを見る限り、バイナリ関連で超めんどくさそうな感じがします。

    というわけで、今回はこのエラーの発生した要因から解決まで書いていこうと思います。

    一体何をしたの?

    エラーになった操作

    以下の記事の作品で、オーディオの変換や処理にFFPMEGを使っていたのですが、プログラムの中ではByteIOで、データを通していたのにFFMPEGだけファイルを通して読み書きしなきゃいけないのが、統一性がなくて気持ち悪く思っていました。

    で、調べたところ、標準入出力を使うことが可能であることがわかり、早速やってみた次第です。
    実際に書くとこんな感じ

    result = subprocess.run(cmd,input=InputBIO.read(),stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    buf = io.BytesIO(result.stdout)

    これでファイルからノイズ除去を行ったのですが、その先のwaveライブラリを使った処理でエラーになってしまった感じですね。

    ちなみにこのバイトIOをファイルに書き出しても再生することができるので、余計なデータが含まれていたり、欠損していたりするわけではなさそうです。

    サイズを確認してみる

    とりあえずプロパティでサイズを確認します。
    片方が普通にファイルから読んでファイルに出力したデータ。
    もう片方がバイトIOを使って標準入力して標準出力をバイトIOに読み込んでからファイルに出力したデータ。
    どちらも寸分違わず同じサイズです。もちろん両方再生できます。

    画像

    原因を探る

    最初の100バイトを確認してみた

    サイズが同じなのにエラーが出るということは中身が若干違うということです。今までいろんなファイルを無理やりバイナリで開いて強引に処理してきたことがあるので、今回も同じようにとりあえず見てみます。

    f = open("tmp.wav","rb")
    >>> b = f.read()
    >>> b[0:100]
    b'RIFF\x94PL\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00\x80\xbb\x00\x00\x00\xee\x02\x00\x04\x00\x10\x00LISTh\x00\x00\x00INFOIART\x13\x00\x00\x00Microsoft Game DVR\x00\x00INAM)\x00\x00\x00Minecraft 24w37a'
    f = open("tmp1.wav","rb")
    >>> b = f.read()
    >>> b[0:100]
    b'RIFF\xff\xff\xff\xffWAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00\x80\xbb\x00\x00\x00\xee\x02\x00\x04\x00\x10\x00LISTh\x00\x00\x00INFOIART\x13\x00\x00\x00Microsoft Game DVR\x00\x00INAM)\x00\x00\x00Minecraft 24w37a'

    …いきなり違うじゃありませんか…

    バイナリーエディターで開いてみた

    さすがにこの表記だと見ても全然わからないので、バイナリエディターで開きます。
    差分検索をすると、8バイト違うことが分かりました。
    なるほど、たったこれだけでエラーになるんですね。
    さて、私には知識がないので、これが何のことなのかAIに聞いてみます。

    画像

    そしてその回答がこちら。さすがよくわかってらっしゃる。
    本来ここにファイルサイズが入るはずなのですが、標準入出力を使った方はサイズが不明確なので入っていないという感じでしょうか。
    確かにそれなら納得です。どうりで出力方法が違うだけでデータが違うわけです。

    画像

    そしてこの後もちょっとずつ質問したのですが、だんだん微妙になってきたので自分で調べました。

    話がかみ合わなかった理由としては標準以外のヘッダーで"LIST"というオプションのヘッダーが入っているせいで話がずれていた感じでした。
    確かに右側に書いてあるストリングの方を見ると、LISTと書いてあるところがありますね。

    解決策の考案

    バイナリの解析結果

    というわけで集めた情報から次のことが分かりました。
    今回変換したデータは次のようなバイナリになっていたみたいです。

    • 最初の12バイト固定

    • 次にfmtチャンク24バイト

    • LISTチャンク8+104バイト

    • データチャンク8+データ数

    画像

    で、どうするの?

    まず最初に5バイト目から8バイト目までをファイルサイズ-8のバイト数になるように置き換えます。
    次に99バイト目から102バイト目までをファイルサイズ-156に置き換えます。
    156は固定12+(fmt8+16)+(LIST8+104)+DATA8です。

    最終コード

    というわけでこちらが修正コードです

    画像

    なんだかんだ調べましたが、結局のところ、チャンクのタイトルを探して、そのサイズを加算するというのが最もシンプルであると考えました。
    というわけで、該当の部分を上書きしたバイトIOを返す関数です。

    結果

    お、今まで進まなかったステップ2に進むようになりました!

    画像

    コード自体の配布

    以下の記事のアップデートで採用予定

    もう1回FFMPEGを通して治すこともできる

    上のコードはもしかしたら完璧ではないかもしれません。なにせ私はこの辺のプロではないので。
    というわけで、もし動かなかった時のための安全策としてFFMPEGを使ってその部分を修正する方法を提案します。
    とは言えただ書き出し直すだけなんですが。

    ffmpeg -i tmp.wav export.wav

    こんな感じでヘッダーが不正なデータを入力して、ファイルとして書き出せば、今回問題になったヘッダーはきちんと値が入っていることが分かりました。
    結局のところ、ファイルに書き出すとヘッダーが設定される仕組みみたいなので、自分で書き換える以外ではこうするしか方法はなさそうですね。

    FFMPEGは入力データのヘッダーの値は特に問題にならないみたいです。念のためオプションとしてこの辺も組み込んでおくとしましょう。


     
     
    モノづくりが趣味。 "売ってないなら自分で作ればいいじゃない"をコンセプトに、機械/電気/AI/プログラム/ネットワーク 全部やります。 記事執筆はAI不使用。 ご連絡や利用規約はプロフィール記事参照。

    あなたへのおすすめ