開発日誌 #5 です。前回は1000ページPDFをフリーズなく表示するTurbo View Engineの話を書きました。
今回は Magic Pipeline、複数の処理を1クリックで連鎖させるワークフローエンジンの設計です。
※8年前のmac book airで撮影して検証をしています。
なぜ作ったか
PDFの定型作業って、毎回同じ手順を繰り返すことが多いです。
例えば:
- スキャンしたPDFを開く
- OCRをかけてテキストを抽出
- ファイルサイズを圧縮
- 特定のフォルダに保存
これを毎回手動でやるのは面倒。かといって複雑なスクリプトを書くほどでもない。
Magic Pipelineはこの「定型作業を1クリックにまとめる」ためのマクロエンジンです。
設計:処理をステップとして定義する
各処理(OCR・圧縮・暗号化・リネームなど)を独立した Step として定義して、それを配列で繋ぎます。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StepType {
Ocr,
Compress { level: CompressionLevel },
Encrypt { password: String },
Rename { template: String },
Save { destination: PathBuf },
Watermark { text: String, opacity: f32 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PipelineStep {
pub step_type: StepType,
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pipeline {
pub name: String,
pub steps: Vec,
}
実行エンジン
パイプラインの実行は前のステップの出力を次のステップの入力として渡す形です。
pub async fn run_pipeline(
pipeline: &Pipeline,
input_path: &Path,
) -> Result {
let mut current_path = input_path.to_path_buf();
for step in pipeline.steps.iter().filter(|s| s.enabled) {
current_path = match &step.step_type {
StepType::Ocr => run_ocr(¤t_path).await?,
StepType::Compress { level } => compress_pdf(¤t_path, level).await?,
StepType::Encrypt { password } => encrypt_pdf(¤t_path, password).await?,
StepType::Rename { template } => rename_file(¤t_path, template).await?,
StepType::Save { destination } => save_to(¤t_path, destination).await?,
StepType::Watermark { text, opacity } => add_watermark(¤t_path, text, *opacity).await?,
};
}
Ok(current_path)
}
各ステップが失敗したら即座にエラーを返し、途中の一時ファイルはクリーンアップします。
Hot Folder:フォルダに置くだけで自動実行
さらに Hot Folder 機能も実装中です。
指定フォルダを監視して、ファイルが追加されたら自動でパイプラインを走らせます。
use notify::{Watcher, RecursiveMode, watcher};
pub fn watch_folder(
folder: &Path,
pipeline: Pipeline,
) -> Result<(), notify::Error> {
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = watcher(tx, Duration::from_secs(1))?;
watcher.watch(folder, RecursiveMode::NonRecursive)?;
loop {
match rx.recv() {
Ok(DebouncedEvent::Create(path)) => {
if path.extension().map_or(false, |e| e == "pdf") {
tokio::spawn(run_pipeline(&pipeline, &path));
}
}
Err(e) => eprintln!("watch error: {:?}", e),
_ => {}
}
}
}
スキャナーの出力先フォルダを指定しておけば、スキャンするだけで自動的にOCR→圧縮→整理まで完結します。
現在の状況(dev版)
ドラッグ&ドロップでステップを並び替えて、パイプラインを組み立てられるUIを実装中です。
次回
次回は Forensic Deep Purge と Stealth Watermark の話を書きます。
「見えないセキュリティ」をどう実装するかという話です。
Hiyoko PDF Vault(日本語) → https://hiyokoko.gumroad.com/l/HiyokoPDFVault_jp
X → @hiyoyok
