2012年2月26日日曜日

【悠々自炊ライフ】自炊始めました!(その3)

自炊のフィルター処理を GIMP でバッチ化できたが、複数の CPU がある場合、有効活用できていない。そんなのい・や・だ!

というわけで、バッチのマルチスレッド化に着手してみた。

基本コンセプトは、バッチ処理をキュー化し、キューを処理するバッチを並列で起動する。
バッチは、すべて windows 標準機能だけで動作するようになっている。
Java とか使えば、もっとシンプルになるのだが、バッチでやることに意味があるのだよ。フッフッフ・・・

【設定ファイル】
set GIMP_CMD="C:\appli_x86\GIMP-2.0\bin\gimp-console-2.6.exe"

@rem キューディレクトリ
set QUEUE_DIR=%~dp0.queues

@rem 処理対象ファイル拡張子
set TARGET_FILES=*.jpg

@rem 上下左右均等切り取りサイズ(ピクセル)
set CROP_SIZE=10

@rem 対象イメージ中の最小サイズに合わせて切り取るフラグ
@rem 以下2つは、断裁時の切断長を補正する目的で利用する。
@rem set ADJUST_SIZE_FLAG=TRUE
set ADJUST_SIZE_FLAG=FALSE

@rem 最小サイズに合わせて切り取る場合に偶数ページは左側、奇数ページは右側を切るフラグ
set ODD_PAGE_ADJUST_LEFT-SIDE_FLAG=TRUE

@rem 画像の拡大縮小(1.0で何もしない)
@rem set SCALE_RATE=0.25
@rem set SCALE_RATE=0.5
set SCALE_RATE=0.75
@rem set SCALE_RATE=1.0

exit /b

【キューを処理するバッチ】
キューを処理するバッチのポイントは、キューがない場合、ping を使って5秒間待つこと。
@echo off
@rem キューディレクトリに登録されたバッチファイルを実行するバッチ
setlocal

@rem 引数チェック
if "%~1"=="" goto usage

set SCRIPT_DIR=%~dp0

:loop1

@rem  処理対象キューディレクトリ
set QUEUE_DIR=%~1

dir "%QUEUE_DIR%" /A:D >nul 2>&1
if not errorlevel 1 goto mainStart
@rem エラーの場合
@echo "%QUEUE_DIR%" が存在しないか、ディレクトリではありません。 1>&2
goto finish

:mainStart

@rem キューディレクトリのバッチファイル件数を取得
set fileCount=0
for %%I in (%QUEUE_DIR%\*.bat) do set /a fileCount=fileCount+1

if %fileCount% equ 0 (
  @rem キューディレクトリのバッチファイルが無い場合 5 秒間ウェイトする
  ping -n 5 localhost > nul
  goto mainStart
)

@rem キューディレクトリのバッチファイルをファイル名昇順で処理
for /f "tokens=*" %%I in ('dir /A:-D /B /O:N "%QUEUE_DIR%\*.bat"') do (
  call :executeBat "%QUEUE_DIR%\%%I"
)

goto mainStart

:executeBat

set TARGET_BAT=%~1

@echo "%TARGET_BAT%" start
@rem キューディレクトリのバッチを実行
call "%TARGET_BAT%"
@rem 処理済のバッチファイルを削除
del "%TARGET_BAT%"

exit /b

@rem 使用法表示
:usage
@echo 使い方:%~nx0 ^<dir^>
@echo 指定されたキューディレクトリのバッチファイルを処理する。
@echo キューディレクトリに格納するファイルは、バッチファイルとする。

:finish
@echo 処理を完了しました。

【キューを処理するバッチを起動するバッチ】
キューディレクトリが、存在しない場合は、キューディレクトリを作成する。
全てのキューディレクトリに対して、キューを処理するバッチを、start コマンドで起動している。
@echo off
@echo キュー処理開始バッチ
setlocal

@rem キュー並行処理数
set QUEUE_CONSUMER_COUNT=4

set SCRIPT_DIR=%~dp0
call "%SCRIPT_DIR%property.bat"

dir "%QUEUE_DIR%" /A:D >nul 2>&1
if not errorlevel 1 goto mainStart
@rem キューディレクトリが存在しない場合
mkdir "%QUEUE_DIR%"

:mainStart

@rem 全てのキューディレクトリに対して、キュー処理バッチを起動
for /L %%I in (1,1,%QUEUE_CONSUMER_COUNT%) do (
  dir "%QUEUE_DIR%\queue%%I" /A:D >nul 2>&1
  @rem キューディレクトリが無い場合は新規作成
  if errorlevel 1 mkdir "%QUEUE_DIR%\queue%%I"
  @rem キュー処理バッチを起動
  start "queue%%I" cmd /c "%SCRIPT_DIR%queue-consumer.bat" %QUEUE_DIR%\queue%%I
)

goto finish

:finish
@echo 処理を完了しました。

【キューを登録するバッチ】
キューを登録するバッチは、結構ヘビーになっている。
要点を列挙する。
  • キューファイルは5桁の連番
  • キュー登録数の少ないキューディレクトリにキューを追加
  • 実行したいバッチを実行するバッチをキューファイルとして作成
@echo off
@echo 自炊用フィルタランチャー
setlocal enabledelayedexpansion

set SCRIPT_DIR=%~dp0

call "%SCRIPT_DIR%property.bat"

@rem 引数チェック
if "%~1"=="" goto usage


set SCRIPT_DIR=%~dp0
set LOG_FILE=%SCRIPT_DIR%\launcher.log
set COLOR_FILTER_ARGS=
set MONO_FILTER_ARGS=
set OCR_FILTER_ARGS=

@rem 全ての引数をループ処理する
:loop1
if "%~1"=="" goto finish
@rem まだ引数がある場合

dir "%~1" /A:D >nul 2>&1
if not errorlevel 1 goto dirCheckOk
@rem エラーの場合
@echo "%~1" が存在しないか、ディレクトリではありません。 2>>%LOG_FILE% 1>&2
shift
goto loop1

:dirCheckOk

set COLOR_DIR=%~1\color
set COVER_DIR=%~1\cover
set MONO_DIR=%~1\mono
set OCR_DIR=%~1\ocr

set COLOR_FILTER_ARGS=""
set COVER_FILTER_ARGS=""
set MONO_FILTER_ARGS=""
set OCR_FILTER_ARGS=""

@rem color ディレクトリの存在をチェック
dir /A:D "%COLOR_DIR%" >nul 2>&1
if errorlevel 1 goto coverDir
@rem *.jpg ファイルの存在をチェック
dir /A:-D "%COLOR_DIR%\*.jpg" >nul 2>&1
if errorlevel 1 goto coverDir
:colorDir
@rem color ディレクトリが存在する場合
set COLOR_FILTER_ARGS="%COLOR_DIR%"

:coverDir
@rem cover ディレクトリの存在をチェック
dir /A:D "%COVER_DIR%" >nul 2>&1
if errorlevel 1 goto monoDir
@rem *.jpg ファイルの存在をチェック
dir /A:-D "%COVER_DIR%\*.jpg" >nul 2>&1
if errorlevel 1 goto monoDir
@rem cover ディレクトリのパスを設定
set COVER_FILTER_ARGS="%COVER_DIR%"

:monoDir
@rem mono ディレクトリの存在をチェック
dir /A:D "%MONO_DIR%" >nul 2>&1
if errorlevel 1 goto ocrDir
@rem *.jpg ファイルの存在をチェック
dir /A:-D "%MONO_DIR%\*.jpg" >nul 2>&1
if errorlevel 1 goto ocrDir
@rem mono ディレクトリのパスを設定
set MONO_FILTER_ARGS="%MONO_DIR%"

:ocrDir
@rem ocr ディレクトリの存在をチェック
dir /A:D "%OCR_DIR%" >nul 2>&1
if errorlevel 1 goto doFilter
@rem *.jpg ファイルの存在をチェック
dir /A:-D "%OCR_DIR%\*.jpg" >nul 2>&1
if errorlevel 1 goto doFilter
@rem ocr ディレクトリのパスを設定
set OCR_FILTER_ARGS="%OCR_DIR%"

:doFilter

@rem キュー追加先ディレクトリ、ファイル名およびテンポラリファイル名を取得
call :getNewQueueFilename

@rem カラーフィルター
if "%COLOR_FILTER_ARGS:"=%"=="" goto coverFilter
@rem テンポラリバッチファイルにカラーフィルターバッチ処理を追加
@echo "%SCRIPT_DIR%color-filter.bat" %COLOR_FILTER_ARGS% >> %newQueuePath%%tempQueueFileName%
@rem テンポラリバッチファイルをリネーム
rename %newQueuePath%%tempQueueFileName% %newQueueFileName%


@rem キュー追加先ディレクトリ、ファイル名およびテンポラリファイル名を取得
call :getNewQueueFilename

:coverFilter
@rem カバーフィルター
if "%COVER_FILTER_ARGS:"=%"=="" goto monoFilter
@rem テンポラリバッチファイルにカバーフィルターバッチ処理を追加
@echo "%SCRIPT_DIR%cover-filter.bat" %COVER_FILTER_ARGS% >> %newQueuePath%%tempQueueFileName%
@rem テンポラリバッチファイルをリネーム
rename %newQueuePath%%tempQueueFileName% %newQueueFileName%


@rem キュー追加先ディレクトリ、ファイル名およびテンポラリファイル名を取得
call :getNewQueueFilename

@rem モノトーンフィルター
:monoFilter
if "%MONO_FILTER_ARGS:"=%"=="" goto ocrFilter
@rem テンポラリバッチファイルにモノトーンフィルターバッチ処理を追加
@echo "%SCRIPT_DIR%monotone-filter.bat" %MONO_FILTER_ARGS% >> %newQueuePath%%tempQueueFileName%
@rem テンポラリバッチファイルをリネーム
rename %newQueuePath%%tempQueueFileName% %newQueueFileName%


@rem キュー追加先ディレクトリ、ファイル名およびテンポラリファイル名を取得
call :getNewQueueFilename

@rem OCR フィルター
:ocrFilter
if "%OCR_FILTER_ARGS:"=%"=="" goto filterEnd
@rem テンポラリバッチファイルにOCR フィルターバッチ処理を追加
@echo "%SCRIPT_DIR%ocr-filter.bat" %OCR_FILTER_ARGS% >> %newQueuePath%%tempQueueFileName%
@rem テンポラリバッチファイルをリネーム
rename %newQueuePath%%tempQueueFileName% %newQueueFileName%


:filterEnd

shift
goto loop1


@rem ------------------------------------------------------
@rem キューを作成するフルパスのファイル名およびテンポラリファイル名を取得
@rem 最もキュー残数の少ないディレクトリが対象となる。
@rem newQueuePath : 新キューパス(戻り値)
@rem newQueueFileName : 新キューファイル名(戻り値)
@rem tempQueueFileName : テンポラリキューファイル名(戻り値)
:getNewQueueFilename

@rem キュー待ち数が少ないディレクトリを取得
set minFileCount=99999
for /f "tokens=*" %%I in ('dir /A:D /B /O:N "%QUEUE_DIR%"') do (
  call :getTargetQueueDir "%QUEUE_DIR%\%%I"
)

@rem 最終キューファイル名を取得
set lastQueueFilename=
for /f "tokens=*" %%I in ('dir /A:-D /B /O:-N "%targetQueueDir%\*.bat" 2^>nul') do (
  set lastQueueFileBaseName=%%~nI
  @rem set lastQueueFileExtention=%%~xI
  goto forend1
)
:forend1

set lastFileBaseNameIn=%lastQueueFileBaseName%
call :getNextNumberFileName

@rem 新キューパス
set newQueuePath=%targetQueueDir%\
@rem 新キューファイル名
set newQueueFileName=%nextFileBaseNameOut%.bat
@rem テンポラリキューファイル名
set tempQueueFileName=%nextFileBaseNameOut%.bat.tmp

exit /b


@rem ------------------------------------------------------
@rem キュー作成先ディレクトリを取得
@rem minFileCount : キュー数の最小値(引数)
@rem targetQueueDir : キュー作成先ディレクトリ(戻り値)
:getTargetQueueDir

@rem ディレクトリ中のファイル数を取得
set fileCount=0
for %%J in ("%~1\*") do set /a fileCount=fileCount+1

@rem キュー待ち数が最も少ないディレクトリを検索
if %fileCount% lss %minFileCount% (
  @rem キュー数の最小値を更新
  set minFileCount=%fileCount%
  @rem キュー作成先ディレクトリ
  set targetQueueDir=%~1
)
exit /b


@rem ------------------------------------------------------
@rem 最終番号+1を返す
@rem 番号は、5桁の先頭0埋めで返す
@rem lastFileBaseNameIn : 最後のファイル名(引数)
@rem nextFileBaseNameOut : 次のファイル名(戻り値)
:getNextNumberFileName

@rem パディング文字
set PAD_ZERO=00001

@rem 番号の桁数
set FIXD_LENGTH=5

if "%lastFileBaseNameIn%"=="" (
  @rem 入力値なしの場合
  set nextFileBaseNameOut=%PAD_ZERO%
  exit /b
)

@rem 最終番号+1を算出
set /a nextFileBaseNameOut=%lastFileBaseNameIn%+1

@rem 番号の桁数を取得
set length=1
set strtmp=%nextFileBaseNameOut%

:looplen1
if "%strtmp:~0,-1%"=="" goto nextlen1
set strtmp=%strtmp:~0,-1%
set /a length=length+1
goto looplen1
:nextlen1

@rem パディング文字から番号の桁数除いたものと番号を結合
set nextFileBaseNameOut=!PAD_ZERO:~0,-%length%!%nextFileBaseNameOut%

exit /b


@rem ------------------------------------------------------
@rem 使用法表示
:usage
@echo 使い方:%~nx0 ^<dir^>
@echo 指定のディレクトリ以下の color cover mono ocr ディレクトリをそれぞれ対応するフィルターで処理を行う。

:finish
@echo 自炊用フィルタ処理が完了しました。何かキーを押してください。
pause > nul