作りたいものが決まったら、すぐに開発に着手します。
今回のアプリの仕様は、自炊(スキャン)したファイルを適切なフォーマットにコンバートして使う事を前提に考えたいと思います。
もちろん、PDFファイルをサーバーに置いといてそのまま単一ページのみを取得するやり方でもいいんですが、どちらにしてもコンバートをしたデータを扱うようにした方がアプリの挙動が保てるので、まずはコンバート方式を作っていきたいと思います。
仕様検討
スキャンデータは、ほぼPDFにするのが一般的です。
自炊後にOCRを実行する場合、読み取ったテキストデータを保持するためには、JPEGやPNGフォーマットでは、難しいですからね。
もちろん、今回のシステム構築としては、そうしたJPEGやPNGファイルをまとめたZIP圧縮データも対象にしようと思うのですが、それらは後回しで開発を進めていきたいと思います。
システムフロー
- ユーザーが手元にあるPDFファイルをシステムのサーバー(今回開発するアプリケーション)にアップロードする。
- PDFデータを受け取ったサーバー側で、PDFファイルを1ページずつ分解してPNG画像に出力する。
- PNGファイルのサイズを最大サイズに整える(小さい画像の場合はそのまま)。
- 画像を1枚ずつWebpフォーマットに変換。
- Webp画像データをBase64(文字列)データに変換。
- サーバー側で取得できたファイルの情報と一緒に、全ての変換した文字列データをJSONフォーマットでテキストファイルとして保存。
技術仕様
最終的にパソコンやスマホアプリに変換しようと思うので、Nodejsで作ったほうがいいと思ったんですが、とりあえずwebサーバー版で安定稼働させたかったので、上記フローはPHPで構築していきたいと思います。
※完成後に、Nodejs移植を考えますね。
PHPはPDFを言語ネイティブでは扱いきれないので、ライブラリをインストールする必要があります。
ここで、PHPのPDFライブラリがたくさんあって、どれが良いかを選別しなければいけなくなりました。
- TCPDF
- mPDF
- FPDF
- html2pdf
- wkhtmltopdf(+snappy)
TCPDFが良いというネット記事をたくさん見ましたが、個人的には、これらのPHPライブラリを使わずに、Linuxの
PDFtoPPMコマンドを使う方法にしました。
理由としては、PHPライブラリのPDFツールは、テキストやHTMLからPDFファイルを作成する事を前提にしているため、PDFを1ページずつ変換するという扱いが便利にできるのが無かったのです。
PDFtoPPMは、PDFを画像にコマンド一発で変換してくれるので、今回の仕様にもってこいという理由ですね。
あと、サーバーコマンドであれば、Dockerでインストールをカッチリ設定しておけば、どのサーバーでもDocker上で安定した動きが保証されるというメリットもあるからですね。
リサイズ処理はGDライブラリを使用
PDFtoPPMでPNGファイルの出力ができたら、次は、画像サイズを調整する処理ですが、これはImageMagicとGDライブラリの2大巨頭のどちらを使うのが良いか考えどころですね。
画像クオリティが良いのは、ImageMagicなんですが、スピードが遅いというのと、バージョンアップが不安定、バグがある・・・などのライブラリ的な問題が混在するようなので、GDライブラリを使うことにしました。
ソースコード
完成後にGithubにアップすて公開する予定なのですが、部分的なソースコードを紹介しておきます。
PDFファイル情報の取得
function pdf($path=null){
$cmd = "pdfinfo {$path}";
$datas = [
"book" => $path,
];
exec($cmd , $res);
for($i=0; $i<count($res); $i++){
if(!$res[$i]){continue;}
$sp = explode(":", $res[$i]);
$key = trim($sp[0]);
$val = trim($sp[1]);
// blank
if($val === ""){
$datas[$key] = null;
}
// number
else if(preg_match("/^\d+?$/" , $val)){
$datas[$key] = (int)$val;
}
// string
else{
$datas[$key] = $val;
}
}
return [
"book" => $path,
"ext" => "pdf",
"pages" => $datas["Pages"],
];
}
$path : サーバー側でファイルを受け取った、$_FILE["file"]["tmp_path"]を送るだけです。
PDF変換処理
function pdf2png($pdf_file=null, $out_path=null , $page=1){
$cmd = "pdftoppm -png {$pdf_file} {$out_path} -f {$page} -l {$page} -cropbox -singlefile";
exec($cmd);
$num = sprintf("%03d" , $page);
return "{$out_path}-{$num}.png";
}
クラス内でPDF変換に使う関数です。
$pdf_file : 変換前のPDFファイル。
$out_path : 出力するファイル名を記載します。(***.png)という感じですね。
$page : 1ページ毎に処理するため、ページ番号を送ります。
PNGをWebpに変換
function png2webp($png_path=null, $webp_path=null, $quality=null){
if(!is_file($png_path)){return;}
$quality = $quality ? $quality : $this->quality;
$png_image = imagecreatefrompng($png_path);
$png_image = $this->resize_image($png_image);
imagewebp($png_image , $webp_path, $quality);
}
function resize_image($image=null){
$max_size = 1000;
$x1 = imagesx($image);
$y1 = imagesy($image);
$x2 = $y2 = $max_size;
// landscape
if($x1 > $y1){
if($x1 < $max_size){
return $image;
}
$rate = $x1 / $max_size;
$x2 = $max_size;
$y2 = floor($y1 / $rate);
}
// horizontal
else{
if($y1 < $max_size){
return $image;
}
$rate = $y1 / $max_size;
$y2 = $max_size;
$x2 = floor($x1 / $rate);
}
$image2 = imagecreatetruecolor($x2, $y2);
imagecopyresampled($image2, $image, 0, 0, 0, 0, $x2, $y2, $x1, $y1);
return $image2;
}
$png_path : 出力されたPNGファイルのパス
$webp_path : アウトプットするwebpのファイルパス
$quality : Webp変換のクオリティ値(0 ~ 100)※50ぐらいで十分です。
あとがき
多くの書籍は、200ページぐらいなんですが、辞書とかみたいに膨大なページ数がある場合を考えて、オライリーのJavascriptの書籍をスキャンしたので変換してみたいと思います。
ページ数は、359ページあり、ファイルサイズが620MBほどあります。
返還時間は、MacBookAir(M2)で、10分ほどかかってしまいました。
ファイルサイズは、なんと、18.3MBです。圧縮率ハンパないですね。
ちなみに、サーバーにはファイルアップロードの上限を設けないととんでもないことになるので、1000MB(1GB)にすることにしました。
※500MBにしていて、オライリー本がコケたので・・・
完成するのが楽しみになってきましたね。
0 件のコメント:
コメントを投稿