やりたかったこと
ffmpeg(ffprobe)では、以下のようにしてstdin(pipe:0)に入力したファイル情報をprobeすることができます。
cat a.mp4 | ffprobe -hide_banner -v error -print_format json -show_streams -i pipe:0
これを利用して、SpringBoot
でフロントからPOSTされたMultipartFile
をファイルとして保存することなくffprobe
の処理を行いたいと考えました。
環境
OS | Java | ffprobe |
---|---|---|
macOS Mojave 10.14.6 | 1.8.0_181 | 4.1.4 |
やり方
ざっくり以下のやり方で一応できます(問題点は後述、Processのstdinに入力することはできていると思います)。
Process
のOutputStream
に書き込みを行うことでファイル内容を渡せます。
org.apache.tika.io.IOUtils
を使っていますが、InputStream
に出力される処理結果を文字列に変換しているだけなので、自力実装するかライブラリを導入するかすればできると思います。
import org.apache.tika.io.IOUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
public static String probeMultipartFile(MultipartFile multipartFile) {
List<String> commands = Arrays.asList(
"ffprobe",
// configure オプションやライブラリのバージョンを出力しない
"-hide_banner",
// ffprobe の内容だけ表示
"-v", "error",
// JSONで出力
"-print_format", "json",
// ファイルに含まれるビデオ・オーディオの情報を配列で出力
"-show_streams",
// パイプに入力される情報を処理する
"-i", "pipe:0"
);
Process process = null;
try {
process = new ProcessBuilder(commands).start();
// stdinへの書き込み
OutputStream os = process.getOutputStream();
os.write(multipartFile.getBytes());
os.flush();
os.close(); // waitFor前にcloseしないと終わらなくなるパターンが有った
// 5秒待って終わらなければ処理を終了してnullを返す
boolean result = process.waitFor(5, TimeUnit.SECONDS);
if (!result) {
process = null;
return null;
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
// IOException Broken pipeとなっていても取れている場合があるのでfinallyでreturnする
if (process != null) {
try {
// processがnullでなければ標準出力を文字列としてreturn
return IOUtils.toString(process.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
問題点
このやり方でファイルを処理してみたところ、手元にあったmp4やwmvファイルでjava.io.IOException: Broken pipe
となってしまう現象が起き、正常系で処理を完結することができませんでした(一応Broken pipeとなるパターンでもファイル指定で実行したprobe結果と同様のものは得られます)。
この問題はどうもファイル容量等には関係なく拡張子に依存して起きているように見えました。
調査は打ち切ってしまったので原因は把握できていませんが、どうもファイルによって以下のように反応が分かれるようです。
-
Process
が適切なタイミングでOutputStream
を閉じる -
OutputStream
が閉じず、書き込み待ちになってしまうため、Java側から閉じないと終了しなくなる -
OutputStream
が異常なタイミングで閉じられてしまい、java.io.IOException: Broken pipe
となる
上2つは正常系内で処理が閉じますが、最後は正常系外で処理を行わなければならないようです。
これ以外にも、stdinから書き込んだ内容に対する処理結果がおかしくなる問題は度々発生しているようなので、大人しくファイルとして保存してから処理を行う方が当たり障りがなくていいのかなと思っています。