0
1

Javaで標準入出力を利用して外部コマンドをフィルターとして利用する

Last updated at Posted at 2024-01-23

はじめに

Java言語で外部コマンドの呼び出しは出来れば避けたいものです。

今回は古いPostscriptファイルの内容をApache Solrに投げ込む必要があり、PDFに変換すればそれ以上の処理を追加する必要がないためGhostscriptパッケージに含まれるps2pdfコマンドを利用することにしました。

検証のため、標準入力にtest.psの内容を書き込み、標準出力からデータを読み出した上でtest.pdfとして保存するコードを作成してみました。実際のファイル形式は*.ps.Zになっているためcommons-compressを利用してdecompressしたbyte配列で取得したPostscriptデータを標準入力に渡しています。

標準入出力では全てのデータをstdinに書き込んでからstdoutへの出力が初まるわけではありません。内部ではstdinに書き込みつつ、制御できないタイミングで発生するstdoutからの読み出し処理を同時に実行する必要があります。内部バッファに収まる程度にファイルサイズが小さければ問題ないかもしれませんが、実際のファイルサイズは小さくはないでしょうから多くの用途でThread化は必須と思われます。

Java言語もずいぶんと簡略化した記法を受け付けてくれるようになっているので、予想よりもずっと完結にコードを書くことができました。

作成した検証用コード

あらかじめ適当にa2psコマンドで作成したtest.psファイルを配置しておきます。

Main.java(検証用コード)
import java.io.*;

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world! (" + System.getProperty("user.dir") + ")");
        try {
            ProcessBuilder procbuild = new ProcessBuilder("ps2pdf", "-", "-");
            Process proc = procbuild.start();
            
            // save the output data as "test.pdf"
            new Thread(() -> {
                    try (InputStream inputStream = proc.getInputStream()) {
                        byte[] inbuff = new byte[4096];
                        int inByteSize;
                        FileOutputStream fos = new FileOutputStream("test.pdf");
                        while ((inByteSize = inputStream.read(inbuff)) != -1) {
                            fos.write(inbuff, 0, inByteSize);
                        }
                    } catch(Exception e) {
                        System.out.println(e.toString());
                    }
            }).start();
            
            // pass the postscript data to stdin
            new Thread(() -> {
                    try (OutputStream outputStream = proc.getOutputStream()) {
                        byte[] outbuff = new byte[4096];
                        int outByteSize;
                        FileInputStream fis = new FileInputStream("test.ps");
                        while ((outByteSize = fis.read(outbuff)) != -1) {
                            outputStream.write(outbuff, 0, outByteSize);
                        }
                        fis.close();
                    } catch(Exception e) {
                        System.out.println(e.toString());
                    }
            }).start();

            proc.waitFor();
        } catch(Exception e) {
            System.out.println(e.toString());
        }
    }
}

Threadを使うことで現実的なスピードで動作するようになりました。

さいごに

Postscriptの処理は商用ライブラリでもネットワーク経由で処理を投げていたり、Javaコードだけで完結しているものはほとんどなさそうです。

Apache Tika 2.8.0以降ではMIMEタイプで"application/illustrator+ps"に対応していますが動作については確認していません。PDFに埋め込まれたEPSファイルの処理がメインのようなのでテキストの抽出は難しそうです。

Ghost4jはghostscriptライブラリへのC言語バインディングで、処理自体はghostscriptで実行されるため外部コマンドの呼び出しよりは細やかな制御が可能ですが、コマンド呼び出しと似たりよったりといった感じです。

無償で入手可能な手段を利用する前提では、ghostscriptを呼び出す方法以外に有力なものはないと思われます。

古いPostscriptの中にはghostscriptで正しく処理できないものがありますが、Adobe Acrobat Proの変換では印刷可能な形式には変換できている状態です。

最終的には古いPostscriptファイルはDistillerの監視フォルダに投入して自動変換する必要があるのかなと思っています。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1