2011年2月3日木曜日

OpenOffice.org と Python マクロ 4 限目

久しぶりに、OOo の Python マクロをいじってみたくなった。
詳しいことは端折るが、リアルタイムスプレッドシートと OpenOffice.org と UWSC でシステムトレーディングの可能性が見えた。最初は、MetaTrader でやるつもりだったが、楽天証券は対応していないようなので、とりあえず茨の道を進んでみる。

で、今回は、野望とは程遠い内容だが、Python マクロを、OpenOffice.org Calc に同梱する方法について、調査した。

【動作環境】
OS:Windows Vista Business 64bit SP2
OpenOffice.org 3.2.1

調査内容については、以下のサイトでのやりとりの内、今回必要な部分をまとめただけとなる。回答されている方には、足を向けて寝られない。
faq/5/430 - OpenOffice.org Q&A
http://oooug.jp/faq/index.php?faq/5/430

要点をまとめると、
  1. デフォルトの状態では、OpenOffice.org 上から、Python マクロを .ods ファイルに同梱する機能がない。
  2. 拡張機能を利用しない場合、Python マクロを .ods ファイルに同梱するには、ファイルを直接変更する必要がある。
  3. 拡張機能をインストールすることによって、OpenOffice.org 上で Python マクロを同梱したり、エディタで開いたりすることができるようなる。

では、手動でのマクロファイル同梱方法から解説する。

1.ファイルを直接変更して、Python マクロを同梱する方法。
詳細は、以下のサイトを参照のこと
http://udk.openoffice.org/python/scriptingframework/index.html

概要を説明すると、.ods ファイル(実は zip ファイル)を解凍して、Scripts/python/ ディレクトリを作成し、Python マクロファイルを配置する。
次に、META-INF/manifest.xml ファイルに前述の Python マクロファイルおよびディレクトリの定義を追加する。
最後に、再び zip 圧縮して、拡張子を .ods に変更する。

もし、Python マクロファイルだけ追加して、manifest.xml ファイルを変更しなかった場合、
「ファイル 'ほげほげ.ods' は汚染されているため、開くことができません。OpenOffice.org で修復を試してください。この汚染は、データ送信に伴う、ドキュメントの改ざんまたは構造的なドキュメントの損傷によって引き起こされた可能性があります。修復されたドキュメントの内容は信用しないことを推奨します。このドキュメントのマクロの実行は無効化されます。」などと怒られ、修復すると、きれいさっぱり Python マクロファイルが消える。ちなみに、修復をしないと .ods ファイルが開けない。


Python マクロファイルを同梱する手順を、実際に試してみた。
  • 適当なディレクトリに、「OpenDocument 表計算」を”Hello.ods”というファイル名で作成。
  • Hello.ods ファイルを zip 解凍ツールで解凍(拡張子を zip に変更する必要があるかもしれない)。
  • 以下のファイルおよびディレクトリが解凍される。
    Configurations2/
    META-INF/
    Thumbnails/
    content.xml
    meta.xml
    mimetype
    settings.xml
    styles.xml
  • 上記の階層に、Scripts/python/ ディレクトリを作成。
  • 作成したディレクトリに、適当な Python マクロファイル(今回は、C:\Program Files (x86)\OpenOffice.org 3\Basis\share\Scripts\python\HelloWorld.py を利用)を配置。
  • META-INF/manifest.xml ファイルをエディタで開き、以下のように21行目~23行目の内容を追加する。
<?xml version="1.0" encoding="UTF-8"?>
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">
 <manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.spreadsheet" manifest:version="1.2" manifest:full-path="/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/statusbar/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/accelerator/current.xml"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/accelerator/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/floater/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/popupmenu/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/progressbar/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/menubar/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/toolbar/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/images/Bitmaps/"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/images/"/>
 <manifest:file-entry manifest:media-type="application/vnd.sun.xml.ui.configuration" manifest:full-path="Configurations2/"/>
 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml"/>
 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="styles.xml"/>
 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="meta.xml"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Thumbnails/thumbnail.png"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Thumbnails/"/>
 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="settings.xml"/>
 <manifest:file-entry manifest:media-type="" manifest:full-path="Scripts/python/HelloWorld.py"/>
 <manifest:file-entry manifest:media-type="application/binary" manifest:full-path="Scripts/python/"/>
 <manifest:file-entry manifest:media-type="application/binary" manifest:full-path="Scripts/"/>
</manifest:manifest>

  • 再び zip 圧縮をする。
  • 圧縮したファイル名を Hello.ods に変更する。
  • Hello.ods ファイルを OpenOffice.org で開く
成功すると、「セキュリティ警告」ダイアログが開き、「ドキュメントマクロを含むドキュメントです。マクロには、ウィルスが含まれていることがあります。安全のためには、ドキュメントのマクロを無効にすることをお勧めします。ただし、マクロを無効にすると、ドキュメントマクロの機能を利用出来なくなりいます。」と警告されるので、「マクロの有効化」ボタンを押すと、めでたくマクロが利用可能になる。

「ツール」メニュー -> 「マクロ」 -> 「マクロの管理」 -> 「Python」を選択
ツリーの「Hello.ods」以下に、”HelloWorld”マクロが含まれていることが確認できる。

以上が、手動による Python マクロファイルの同梱手順だが、面倒くさい。
そこで、拡張機能の出番。

以下のサイトにアクセスする。
http://hermione.s41.xrea.com/pukiwiki/index.php?OOobbs2%2F194

上記サイトで以下のファイルをダウンロードして、ファイルをダブルクリックすると、OpenOffice.org の拡張マネージャ画面が起動するので、それぞれインストールする。
EditorKicker-0.1.0.oxt
ModifiedPythonScriptProvider-0.4.2.oxt

OpenOffice.org を再起動する。
OpenOffice.org の「ツール」メニュー -> 「拡張機能マネージャ」を選択する。
「EditorKicker」を選択し、「オプション」ボタンをクリックする。

「Editor」の「Choose」ボタンを押して、エディターを選択する。
私の場合は、”C:\appli_x86\sakura\sakura.exe”
を選択した。

「Options」には、「-Y=${COL} -X=${ROW} -- "${FILENAME}"」のように設定した。ファイル名をダブルクォートで囲む必要がある。でないと、スペース入りのディレクトリや、ファイル名で問題が起こる。

「OK」ボタンを押す。

「拡張機能マネージャー」の「閉じる」ボタンを押す。
OpenOffice.org を再起動する。必ずすること。でないと設定が反映されないようだ。

これでインストールは完了。

この拡張機能は、OpenOffice.org の「ツール」メニュー ->「マクロ」 -> 「マクロの管理」 -> 「Python」を選択すると表示される、「Python マクロ」管理画面に対して機能が追加される。
具体的には、マクロファイルに対して、「作成」、「編集」、「名前の変更」、「削除」ボタンなどが利用できるようになる。


この拡張機能の利用方法で、理解に時間がかかった部分を補足説明する。

■ ファイル同梱のマクロファイルとそれ以外では、「編集」ボタンの挙動が異なる。(しばらく意味が分からず、不具合かと思ったwww)


「あなたのマクロ」および「OpenOffice.org マクロ」のマクロファイルに対しては、エディターが起動する。
ファイル同梱のマクロファイルに対しては、ファイル選択ダイアログが開く。Python マクロファイルを選択すると、ファイル同梱のマクロファイルの内容が、選択したファイルの内容で上書きされる。

■ 一度ファイル同梱のマクロを作成してしまうと、マクロファイルを削除しても、起動時にマクロに関する警告が表示される。
これは、Scripts ディレクトリが残るためだ。前述の手動での同梱マクロ追加方法を参考に、Scripts ディレクトリと設定を削除することで、マクロに関する警告が表示されなくなる。

また一歩、Python マクロの深みに嵌っていく・・・・

冒頭の FAQ でのやり取りから、マクロの配布形態は、「ユーザー共通」、「ユーザー個別」、「ファイル同梱」、そして、「拡張機能」形式が存在することが分かった。「拡張機能」形式には、アップデートが容易などの利点があるので、興味はあるが、もはや、目的が何だったのか忘れる勢い・・・さらなる茨の道を進む・・・のか?

2011年1月30日日曜日

サーバまるごとバックアップじゃないかもしれないスクリプト

tar を使った、サーバまるごとバックアップじゃないかもしれないスクリプト
完全にオレオレ仕様。録画サーバのバックアップ目的。録画したデータは、除外している。

【backupall.sh】

#!/bin/sh

BACKUP_FILEBASE="backupall_"
BACKUP_FILENAME="${BACKUP_FILEBASE}`date +"%Y%m%d_%H%M"`.tar.gz"
cd /
sudo tar cpzf /${BACKUP_FILENAME} --exclude="${BACKUP_FILEBASE}*" --exclude="cdrom" --exclude="dev" --exclude="lost+found" --exclude="media" --exclude="mnt" --exclude="proc" --exclude="sys" --exclude="tmp" --exclude="var/cache" --exclude="var/lock" --exclude="var/run" --exclude="var/tmp" --exclude="var/www/epgrec/video" *

リポジトリバックアップ用スクリプト

リポジトリバックアップ用スクリプトを書いた。主に自分用に晒しておく。
ファイル名にリビジョン番号を含めておいて、バックアップ時にリビジョン番号がその番号を超えていれば、バックアップをするしくみ。

【svnbackup.sh】

#!/bin/sh

# バックアップ先ディレクトリ
BACKUP_BASE_DIR=/mnt/sdb1/backups/svn
# リポジトリベースディレクトリ
SVN_BASE_DIR=/var/svn
# リポジトリディレクトリ名リスト
REPO_NAMES="InvestmentSimulator gameProjectFiles private tamu trac_ja"

svn_backup()
{
  TARGET_NAME=$1

  if [ ! -d "${SVN_BASE_DIR}/${TARGET_NAME}" ]; then
    echo ${TARGET_NAME} は、存在しません。
    return
  fi

  # 最新のリビジョンを取得
  NEW_REV=`svnlook youngest ${SVN_BASE_DIR}/${TARGET_NAME}`

  # 前回バックアップ時のリビジョンを取得
  LAST_REV=`get_last_revision ${TARGET_NAME}`

  if [ "${LAST_REV}" -ge "${NEW_REV}" ]; then
    # 現在のリビジョンが前回バックアップ時のリビジョン以下の場合
    echo ${TARGET_NAME} は、前回バックアップ時と同じ状態です。
    return
  fi

  sudo -u www-data svnadmin hotcopy ${SVN_BASE_DIR}/${TARGET_NAME} ${BACKUP_BASE_DIR}/${TARGET_NAME}
  (cd ${BACKUP_BASE_DIR}; tar zpcf ${TARGET_NAME}_${NEW_REV}.tar.gz ${TARGET_NAME})
  rm -rf ${BACKUP_BASE_DIR}/${TARGET_NAME}
  echo ${TARGET_NAME} をバックアップしました。
}

get_last_revision()
{
  TARGET_NAME=$1
  # 前回のバックアップファイル名を取得
  LAST_BACKUP_FILENAME=`basename \`ls -1r ${BACKUP_BASE_DIR}/${TARGET_NAME}* 2>/dev/null | head -1\` 2>/dev/null`
  if [ -n "${LAST_BACKUP_FILENAME}" ]; then
    # 前回バックアップ時のリビジョンを取得
    LAST_REV=`echo ${LAST_BACKUP_FILENAME} | sed -e "s/.*_\([0-9]\+\)\.tar\.gz/\1/"`
    echo ${LAST_REV}
  else
    echo "-1"
  fi
}

for reponame in ${REPO_NAMES}
do
  svn_backup ${reponame}
done

2011年1月5日水曜日

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

さて今回は、自炊用の各スクリプトの解説をおこなっていきたい。長い上に、見づらいので悪しからず。

まず、概要から。
スクリプトは、GIMP 上で実行する Script-Fu スクリプトファイルと、それを呼び出すバッチファイルの2種類を作成する。
Script-Fu の格納場所は”<インストールディレクトリ>\GIMP-2.0\share\gimp\2.0\scripts”

バッチファイルは、適宜、好きな場所に置くこと。

■ Script-Fu スクリプト
では、GIMP 上で実行する Script-Fu スクリプトの解説から
それぞれのスクリプトは、GIMP を起動して、「フィルタ」メニューの「AA-Filters」からも実行できる。ただし、その場合フィルター処理のみで、ファイルの保存などは行われない。

始めに、各フィルタの共通処理を記述したスクリプト。どうやら、定義した関数は、他のスクリプトファイルからでも利用可能なようだ。裏を返せば、他の関数名と重複してはいけないということだね。
ファイル名リンクをクリックすると、ファイルがダウンロードできる。
[ AA-jisui-common.scm ]

;=====================================================================
; 自炊用フィルタ共通処理
;
;=====================================================================

;=====================================================================
;
; フルパス指定のファイル名から、拡張子を除いたファイル名を取得
;
;=====================================================================
(define (AA-jisui-common-strip-base-filename full-path-filename)
    (let*
        (
            ; フルパスからファイル名(拡張子を含む)のみを取得(パス区切り文字で分割して、最後の要素を取得)
            (filename (car (last (strbreakup full-path-filename DIR-SEPARATOR))))

            ; ファイル名から拡張子を除いたものを取得("."で分解して、最後を除く要素を"."で結合)
            (filename-without-ext (unbreakupstr (butlast (strbreakup filename ".")) "."))
        )
        filename-without-ext
    )
) 

カラー用のフィルタ。
切抜きと、ノイズ除去処理を行っている。
[ AA-jisui-color-filter.scm ]
;=====================================================================
; 自炊用フィルタ(カラー用)
; カレントフォルダ内の処理対象ファイル名パターンに一致する画像を開き、
; 画像処理後 Output フォルダへ保存するスクリプト
;
; コマンド入力例
; gimp-2.6 -i -b "(AA-jisui-color-filter-main \"C:\\image\\*.jpg\" \"C:\\image\\Output\" 30)" -b "(gimp-quit 0)"
;
; pattern: 処理対象ファイル名パターン
; outputdir: 出力ディレクトリ
; crop-size: クロップサイズ
;
;=====================================================================
(define (AA-jisui-color-filter-main pattern outputdir crop-size)
    (gimp-message-set-handler CONSOLE)
    (gimp-message "自炊用画像フィルタ(カラー用)処理開始")

    (let*
        ; カレントフォルダ内にあるファイルリスト取得
        ((filelist (cadr (file-glob pattern 1))))

        ; ループ処理
        (while (not (null? filelist))
            (let*
                (
                    ; 変数宣言および値の取得
                    (in-filename (car filelist))
                    (image (car (gimp-file-load RUN-NONINTERACTIVE in-filename in-filename)))
                    (drawable (car (gimp-image-get-active-layer image)))
                    ; フルパスの入力ファイル名から拡張子除いたファイル名を取得
                    (out-filename-without-ext (AA-jisui-common-strip-base-filename in-filename))
                    ; 出力先フルパス文字列(拡張子を除く)を生成
                    (out-filepath-without-ext (string-append outputdir DIR-SEPARATOR out-filename-without-ext))
                )
                (gimp-message (string-append in-filename " ファイルの処理を開始"))

                ; 画像を処理
                (AA-jisui-color-filter image drawable crop-size)

                ; JPEG 形式でファイル保存
                (let*
                    (
                        ; ファイル名生成
                        (out-filename (string-append out-filepath-without-ext ".jpg"))
                    )
                    (file-jpeg-save RUN-NONINTERACTIVE image drawable out-filename out-filename
                        ; quality (0<=/<=1)
                        1
                        ; smoothing (0<=/<=1)
                        0
                        ; optimize (0/1)
                        1
                        ; progressive (0/1)
                        0
                        ; comment
                        ""
                        ; subsmp
                        0
                        ; baseline
                        1
                        ; restart
                        0
                        ; dct
                        0
                    )
                    (gimp-message (string-append out-filename " ファイルを保存"))
                )

                ; イメージの削除
                (gimp-image-delete image)
            )
            (set! filelist (cdr filelist))
        )
    )
)

;============================================
;
; フィルタ処理
;
;============================================
(define (AA-jisui-color-filter image drawable crop-size)
    (gimp-message-set-handler CONSOLE)
    ; 切り抜き処理
    (if (> crop-size 0)
        (begin
            (gimp-message "切り抜き処理開始")
            (let*
                (
                    ; 関数の戻り値は、かならず List 形式になっているので、car が必須
                    (width (car (gimp-image-width image)))
                    (height (car (gimp-image-height image)))
                )
                (gimp-image-crop image
                    (- width (* crop-size 2))
                    (- height (* crop-size 2))
                    crop-size
                    crop-size
                )
            )
        )
    )

    ; ノイズ除去処理
    (gimp-message "ノイズ除去処理開始")
    (plug-in-despeckle RUN-NONINTERACTIVE image drawable
        ; 半径
        2
        ; 中央値
        1
        ; 黒レベル
        1
        ; 白レベル
        255
    )

    ; 再描画
    (gimp-displays-flush)
)

;============================================
;
; スクリプト定義
;
;============================================
(script-fu-register
    ; スクリプト名
    "AA-jisui-color-filter"
    ; メニュー名
    "<Image>/Filters/AA-Filters/jisui-color-filter"
    ; 詳細
    "自炊用フィルタ(カラー用)"
    ; 作成者
    "AbsoluteArea"
    ; コピーライト
    "copyright 2011, AbsoluteArea"
    ; 作成日
    "Jan 1, 2011"
    ; 動作モード指定
    "RGB*"

    SF-IMAGE "Image" 0
    SF-DRAWABLE "Drawable"  0
    SF-VALUE "Crop Size (pixels)" "30"
);end of register


モノトーン用フィルタ
切り抜き、脱色、ノイズ除去、色レベル調整、アンシャープマスク処理を行っている。
[ AA-jisui-monotone-filter.scm ]
;=====================================================================
; 自炊用フィルタ(モノトーン用)
; カレントフォルダ内の処理対象ファイル名パターンに一致する画像を開き、
; 画像処理後 Output フォルダへ保存するスクリプト
;
; コマンド入力例
; gimp-2.6 -i -b "(AA-jisui-monotone-filter-main \"C:\\image\\*.jpg\" \"C:\\image\\Output\" 30 FALSE)" -b "(gimp-quit 0)"
;
; pattern: 処理対象ファイル名パターン
; outputdir: 出力ディレクトリ
; crop-size: クロップサイズ
; unsharp-flag: アンシャープマスクフラグ
;
;=====================================================================
(define (AA-jisui-monotone-filter-main pattern outputdir crop-size unsharp-flag)
    (gimp-message-set-handler CONSOLE)
    (gimp-message "自炊用画像フィルタ(モノトーン用)処理開始")

    (let*
        ; カレントフォルダ内にあるファイルリスト取得
        ((filelist (cadr (file-glob pattern 1))))

        ; ループ処理
        (while (not (null? filelist))
            (let*
                (
                    ; 変数宣言および値の取得
                    (in-filename (car filelist))
                    (image (car (gimp-file-load RUN-NONINTERACTIVE in-filename in-filename)))
                    (drawable (car (gimp-image-get-active-layer image)))
                    ; 入力ファイル名から拡張子除いたものを生成
                    (out-filename-without-ext (AA-jisui-common-strip-base-filename in-filename))
                    ; 出力先フルパス文字列(拡張子を除く)を生成
                    (out-filepath-without-ext (string-append outputdir DIR-SEPARATOR out-filename-without-ext))
                )
                (gimp-message (string-append in-filename " ファイルの処理を開始"))

                ; 画像を処理
                (AA-jisui-monotone-filter image drawable crop-size unsharp-flag)

                ; JPEG 形式でファイル保存
                (let*
                    (
                        ; ファイル名生成
                        (out-filename (string-append out-filepath-without-ext ".jpg"))
                    )
                    (file-jpeg-save RUN-NONINTERACTIVE image drawable out-filename out-filename
                        ; quality (0<=/<=1)
                        1
                        ; smoothing (0<=/<=1)
                        0
                        ; optimize (0/1)
                        1
                        ; progressive (0/1)
                        0
                        ; comment
                        ""
                        ; subsmp
                        0
                        ; baseline
                        1
                        ; restart
                        0
                        ; dct
                        0
                    )
                    (gimp-message (string-append out-filename " ファイルを保存"))
                )

                ; イメージの削除
                (gimp-image-delete image)
            )
            (set! filelist (cdr filelist))
        )
    )
)

;============================================
;
; フィルター処理
;
;============================================
(define (AA-jisui-monotone-filter image drawable crop-size unsharp-flag)
    (gimp-message-set-handler CONSOLE)
    ; 切り抜き処理
    (if (> crop-size 0)
        (begin
            (gimp-message "切り抜き処理開始")
            (let*
                (
                    ; 関数の戻り値は、かならず List 形式になっているので、car が必須
                    (width (car (gimp-image-width image)))
                    (height (car (gimp-image-height image)))
                )
                (gimp-image-crop image
                    (- width (* crop-size 2))
                    (- height (* crop-size 2))
                    crop-size
                    crop-size
                )
            )
        )
    )

    ; 脱色処理
    (gimp-message "脱色処理開始")
    (gimp-desaturate drawable)

    ; ノイズ除去処理
    (gimp-message "ノイズ除去処理開始")
    (plug-in-despeckle RUN-NONINTERACTIVE image drawable
        ; 半径
        2
        ; 中央値
        1
        ; 黒レベル
        1
        ; 白レベル
        255
    )

    ; 色レベル調整処理
    (gimp-message "色レベル調整処理開始")
    (gimp-levels drawable HISTOGRAM-VALUE
        ; low-input
        60
        ; high-input
        225
        ; gamma
        1.0
        ; low-output
        0
        ; high-output
        255
    )

    ; アンシャープマスク処理
    (if (= unsharp-flag TRUE)
        (begin
            (gimp-message "アンシャープマスク処理開始")
            (plug-in-unsharp-mask RUN-NONINTERACTIVE image drawable
                ; 半径
                15.0
                ; 量
                4.00
                ; 閾値
                0
            )
        )
    )

    ; 再描画
    (gimp-displays-flush)
)

;============================================
;
; スクリプト定義
;
;============================================
(script-fu-register
    ; スクリプト名
    "AA-jisui-monotone-filter"
    ; メニュー名
    "<Image>/Filters/AA-Filters/jisui-monotone-filter"
    ; 詳細
    "自炊用フィルタ(モノトーン用)"
    ; 作成者
    "AbsoluteArea"
    ; コピーライト
    "copyright 2011, AbsoluteArea"
    ; 作成日
    "Jan 1, 2011"
    ; 動作モード指定
    "RGB*"

    SF-IMAGE "Image" 0
    SF-DRAWABLE "Drawable"  0
    SF-VALUE "Crop Size (pixels)" "30"
    SF-TOGGLE "UnSharp Mask" FALSE
);end of register


OCR 用フィルタ
切り抜き、脱色、ノイズ除去 、色レベル調整、脱色、二値化処理を行っている。
[ AA-jisui-OCR-filter.scm ]
;=====================================================================
; 自炊用フィルタ(OCR用)
; カレントフォルダ内の処理対象ファイル名パターンに一致する画像を開き、
; 画像処理後 Output フォルダへ保存するスクリプト
;
; コマンド入力例
; gimp-2.6 -i -b "(AA-jisui-ocr-filter-main \"C:\\image\\*.jpg\" \"C:\\image\\Output\" 30)" -b "(gimp-quit 0)"
;
; pattern: 処理対象ファイル名パターン
; outputdir: 出力ディレクトリ
; crop-size: クロップサイズ
;
;=====================================================================
(define (AA-jisui-ocr-filter-main pattern outputdir crop-size)
    (gimp-message-set-handler CONSOLE)
    (gimp-message "自炊用画像フィルタ(OCR用)処理開始")

    (let*
        ; カレントフォルダ内にあるファイルリスト取得
        ((filelist (cadr (file-glob pattern 1))))

        ; ループ処理
        (while (not (null? filelist))
            (let*
                (
                    ; 変数宣言および値の取得
                    (in-filename (car filelist))
                    (image (car (gimp-file-load RUN-NONINTERACTIVE in-filename in-filename)))
                    (drawable (car (gimp-image-get-active-layer image)))
                    ; 入力ファイル名から拡張子除いたものを生成
                    (out-filename-without-ext (AA-jisui-common-strip-base-filename in-filename))
                    ; 出力先フルパス文字列(拡張子を除く)を生成
                    (out-filepath-without-ext (string-append outputdir DIR-SEPARATOR out-filename-without-ext))
                )
                (gimp-message (string-append in-filename " ファイルの処理を開始"))

                ; 画像を処理
                (AA-jisui-ocr-filter image drawable crop-size)

                ; JPEG 形式でファイル保存
                (let*
                    (
                        ; ファイル名生成
                        (out-filename (string-append out-filepath-without-ext ".jpg"))
                    )
                    (file-jpeg-save RUN-NONINTERACTIVE image drawable out-filename out-filename
                        ; quality (0<=/<=1)
                        1
                        ; smoothing (0<=/<=1)
                        0
                        ; optimize (0/1)
                        1
                        ; progressive (0/1)
                        0
                        ; comment
                        ""
                        ; subsmp
                        0
                        ; baseline
                        1
                        ; restart
                        0
                        ; dct
                        0
                    )
                    (gimp-message (string-append out-filename " ファイルを保存"))
                )

                ; イメージの削除
                (gimp-image-delete image)
            )
            (set! filelist (cdr filelist))
        )
    )
)

;============================================
;
; フィルター処理
;
;============================================
(define (AA-jisui-ocr-filter image drawable crop-size)
    (gimp-message-set-handler CONSOLE)
    ; 切り抜き処理
    (if (> crop-size 0)
        (begin
            (gimp-message "切り抜き処理開始")
            (let*
                (
                    ; 関数の戻り値は、かならず List 形式になっているので、car が必須
                    (width (car (gimp-image-width image)))
                    (height (car (gimp-image-height image)))
                )
                (gimp-image-crop image
                    (- width (* crop-size 2))
                    (- height (* crop-size 2))
                    crop-size
                    crop-size
                )
            )
        )
    )

    ; 脱色処理
    (gimp-message "脱色処理開始")
    (gimp-desaturate drawable)

    ; ノイズ除去処理
    (gimp-message "ノイズ除去処理開始")
    (plug-in-despeckle RUN-NONINTERACTIVE image drawable
        ; 半径
        2
        ; 中央値
        1
        ; 黒レベル
        1
        ; 白レベル
        255
    )

    ; 色レベル調整処理
    (gimp-message "色レベル調整処理開始")
    (gimp-levels drawable HISTOGRAM-VALUE
        ; low-input
        60
        ; high-input
        225
        ; gamma
        1.0
        ; low-output
        0
        ; high-output
        255
    )

    ; アンシャープマスク処理
    (gimp-message "アンシャープマスク処理開始")
    (plug-in-unsharp-mask RUN-NONINTERACTIVE image drawable
        ; 半径
        15.0
        ; 量
        4.00
        ; 閾値
        0
    )

    ; 二値化処理
    (gimp-message "二値化処理開始")
    (gimp-threshold drawable 245 255)

    ; 再描画
    (gimp-displays-flush)
)

;============================================
;
; スクリプト定義
;
;============================================
(script-fu-register
    ; スクリプト名
    "AA-jisui-ocr-filter"
    ; メニュー名
    "<Image>/Filters/AA-Filters/jisui-ocr-filter"
    ; 詳細
    "自炊用フィルタ(OCR用)"
    ; 作成者
    "AbsoluteArea"
    ; コピーライト
    "copyright 2011, AbsoluteArea"
    ; 作成日
    "Jan 1, 2011"
    ; 動作モード指定
    "RGB*"

    SF-IMAGE "Image" 0
    SF-DRAWABLE "Drawable"  0
    SF-VALUE "Crop Size (pixels)" "30"
);end of register



■ バッチファイル
次に、Windows のバッチファイルを説明する。
各バッチを実行する前に、バッチ中の”GIMP_CMD”変数に、各自の環境に合わせた GIMP の実行ファイルのパスを指定する必要がある。これ重要。
使用方法は、処理対象のディレクトリをドラッグアンドドロップする。

実行する際には、GIMP が起動している必要は無い。また、GIMP が起動していても実行可能である。
各バッチは、複数のディレクトリを指定できるようになっている。
ディレクトリ名やファイル名は、スペースを含んでいても動作するようにがんばった。おかげで、バッドノウハウが沢山溜まった。


カラー用フィルタバッチ
[ color-filter.bat ]
@echo off
@echo 自炊用フィルタ(カラー用)
setlocal
set GIMP_CMD="C:\appli_x86\GIMP-2.0\bin\gimp-console-2.6.exe"
set GIMP_BAT_FUNC=AA-jisui-color-filter-main
set TARGET_FILES=*.jpg
set CROP_SIZE=10

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

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

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

:dirCheckOk

set INPUT_DIR=%~1\
set OUTPUT_DIR=%~1\Output
set GIMP_BATCH_OPT=%GIMP_BATCH_OPT% -b "(%GIMP_BAT_FUNC% \"%INPUT_DIR:\=\\%%TARGET_FILES%\" \"%OUTPUT_DIR:\=\\%\" %CROP_SIZE%)"
@rem 出力先ディレクトリ作成
if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
shift
goto loop1

:execGimp
@rem 画像処理実行
@echo %GIMP_CMD% -i %GIMP_BATCH_OPT% -b "(gimp-quit 0)"
%GIMP_CMD% -i %GIMP_BATCH_OPT% -b "(gimp-quit 0)"

goto finish

@rem 使用法表示
:usage
@echo 使い方:%~nx0 ^<dir^>

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


モノトーン用フィルタバッチ
 [ monotone-filter.bat ]
@echo off
@echo 自炊用フィルタ(モノトーン用)
setlocal
set "GIMP_CMD=C:\appli_x86\GIMP-2.0\bin\gimp-console-2.6.exe"
set GIMP_BAT_FUNC=AA-jisui-monotone-filter-main
set TARGET_FILES=*.jpg
set CROP_SIZE=30
set UNSHARP_FLAG=TRUE

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

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

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

:dirCheckOk
set INPUT_DIR=%~1\
set OUTPUT_DIR=%~1\Output
set GIMP_BATCH_OPT=%GIMP_BATCH_OPT% -b "(%GIMP_BAT_FUNC% \"%INPUT_DIR:\=\\%%TARGET_FILES%\" \"%OUTPUT_DIR:\=\\%\" %CROP_SIZE% %UNSHARP_FLAG%)"
@rem 出力先ディレクトリ作成
if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
shift
goto loop1

:execGimp
@rem 画像処理実行
@echo %GIMP_CMD% -i %GIMP_BATCH_OPT% -b "(gimp-quit 0)"
%GIMP_CMD% -i %GIMP_BATCH_OPT% -b "(gimp-quit 0)"

goto finish

@rem 使用法表示
:usage
@echo 使い方:%~nx0 ^<dir^>

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


OCR 用フィルタバッチ
[ ocr-filter.bat ]
@echo off
@echo 自炊用フィルタ(OCR用)
setlocal
set GIMP_CMD="C:\appli_x86\GIMP-2.0\bin\gimp-console-2.6.exe"
set GIMP_BAT_FUNC=AA-jisui-ocr-filter-main
set TARGET_FILES=*.jpg
set CROP_SIZE=30

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

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

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

:dirCheckOk
set INPUT_DIR=%~1\
set OUTPUT_DIR=%~1\Output
set GIMP_BATCH_OPT=%GIMP_BATCH_OPT% -b "(%GIMP_BAT_FUNC% \"%INPUT_DIR:\=\\%%TARGET_FILES%\" \"%OUTPUT_DIR:\=\\%\" %CROP_SIZE%)"
@rem 出力先ディレクトリ作成
if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
shift
goto loop1

:execGimp
@rem 画像処理実行
@echo %GIMP_CMD% -i %GIMP_BATCH_OPT% -b "(gimp-quit 0)"
%GIMP_CMD% -i %GIMP_BATCH_OPT% -b "(gimp-quit 0)"

goto finish

@rem 使用法表示
:usage
@echo 使い方:%~nx0 ^<dir^>

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


最後に、便利ツールとして、ディレクトリ中の .jpg ファイルを 001 から始まる連番でリネームするバッチファイル。
このバッチファイルも、複数のディレクトリを一括指定できる。
拡張子を除くファイル名の末尾を指定文字数削って、連番をつけることも出来る。これは、既に連番が付いているものを付け直す用途を想定している。
[ renumber.bat ]
@echo off
@rem ===========================================
@rem 指定したディレクトリの *.jpg ファイルをファイル名順に連番つきでリネームする
@rem リネーム時にファイル名の末尾を削ってから連番を付与する指定もできる。img398.jpg -> img001.jpg など
@rem 複数のディレクトリを一括して処理可能
@rem スペースを含むディレクトリやファイルも処理可能
@rem ===========================================

setlocal enabledelayedexpansion

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

@rem メイン処理
:main
@rem 元ファイル名の末尾を削る桁数
set REPLACE_NUM_SIZE=3
@rem 連番の桁数
set NUM_SIZE=3
@rem 固定長表示用のパディング文字(NUM_SIZE - 1 文字設定すること)
set PAD_STR=00

@rem 引数の個数分ループする
:mainLoop

@rem 処理対象ディレクトリ
set targetDir=%~1

if "%targetDir%"=="" goto finish

@rem バッドノウハウ:"%~1\nul" でディレクトリ存在チェックをしようとすると、スペース入りディレクトリ名の判定に失敗する
@rem さらに Windows XP SP3 などでは、 "%~s1\nul" (短縮ファイル名)で判定しようとすると、
@rem バグのせいで正常に短縮ファイル名が返らないず、正しく判定ができない場合があるようだ
@rem しょうがないので、dir コマンドでディレクトリ一覧の取得に成功したら OK とした。
dir "%targetDir%" /A:D >nul 2>&1
if not errorlevel 1 goto dirCheckOk
@rem ディレクトリでなかった場合
@echo "%~1"  が存在しないか、ディレクトリではありません。
goto finish

:dirCheckOk

@rem 連番用カウンタ
set count=1

@rem *.jpg ファイルをファイル名でソートしてループ処理する
@rem tokens オプションを指定しないと、スペースを含むファイルが処理できない
for /f "tokens=*" %%I in ('dir /A-D /B /O:N "%TARGETDIR%\*.jpg"') do (
    set tmpnum=%PAD_STR%!count!
    set num=!tmpnum:~-%NUM_SIZE%!

    @rem 対象ファイルのフルパス
    set targetFileName=%targetDir%\%%~nxI

    @rem 連番付き新フルパスファイル名を生成
    set fileBase=%%~nI
    set fileExt=%%~xI
    if "%REPLACE_NUM_SIZE%"=="0" (
        @rem バッドノウハウ:%hoge:~0,-0% とすると、先頭0文字目から末尾から0文字目まで(つまり、そのままの値)にならず、空になる。
        set newFileBase=!fileBase!!num!
    ) else (
        set newFileBase=!fileBase:~0,-%REPLACE_NUM_SIZE%!!num!
    )
    set newFileName=!newFileBase!!fileExt!

    @rem ファイルリネーム実行
    @echo rename "!targetFileName!" "!newFileName!"
    rename "!targetFileName!" "!newFileName!"
    if ERRORLEVEL 1 (
        @echo バッチ処理を中断します。 1>&2
        goto finish
    )

    set /A count+=1
)

shift
goto mainLoop

goto finish


@rem 利用方法
:usage
@echo 使い方: %~nx0 ^<DIR^> [^<DIR^>] ...

:finish
@echo ファイルのリネーム処理が完了しました。何かキーを押してください。
@pause > nul


次回は、実際の作業方法などを解説していく。(予定・・・)

2011年1月4日火曜日

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

自炊といっても、食べるほうじゃないですよ?
自前で本の電子データ化を行うことですよ?

まあ、この用語を知ったのは、つい数日前だけど・・・

というわけで、その筋ではデファクトスタンダードといわれている、Fujitsu ScanSnap S1500(カラースキャナ) と PLUS PK-513L(手動断裁機)を購入して自炊ライフを満喫することになった。

まあ、いずれこうなる運命。6畳の狭い部屋だというのに、大小 400冊を超える本が押入れを占拠し、それほど遠くない未来には、生活スペースを侵略し始めるに違いないからで、さらに、年を食うごとに物忘れが酷くなり、読んだ本の内容が、頭に納まりきらない有様。
「はて?あのフレーズが書いてあった本は、なんだったかいのう?」などなど・・・

であるからして、「欲しい」のではなく、「必要」だったのです。



・・・言い訳はこれぐらいにして、本題に。

■ 環境
Windows Vista Business 64bit SP2
AMD Phenom II X4 955 3.20GHz
メモリ 8GB
Fujitsu ScanSnap S1500(カラースキャナ)
PLUS PK-513L(手動断裁機)
Adobe Acrobat 9.4.1 Standard(バンドル版)
GIMP 2.6.11


■ 目標と野望
目標は、「本を電子データ化し、検索可能にすること」。
野望は、「クラウド上に、電子データ管理システムを構築し、どの端末からでもデータの同期と検索ができる環境を構築すること」。


■ 問題と課題
実際に、数種類の本を取り込んでみた結果、以下の問題点が判明した。
  • OCR の文字認識率が思ったほどよくない。
  • 取り込み時のノイズが、PDF を圧縮したときに顕著に現れる。
  • 画質が「エクセレント(カラー 600 dpi)」の場合は、取り込み速度が半分以下になる。
  • 「原稿の向きを自動的に補正します」を有効にすると、あらぬ方向に回転してることがある。
  • 「文字をくっきりします」は、OCR の認識率を下げるらしい。
  • 「白紙ページを自動的に削除します」は、白紙もページに数えられているときに困る。

現時点での課題は・・・
  • OCR の読み取り精度を向上させる。
  • 将来に再処理できるように原本を残す。
  • 処理の手間を低減する。


■ 方針と対策
問題と課題を踏まえた上で、以下のような方針を立てた。

  1. 取り込み時の設定を見直す。
  2. 取り込み後の画像にフィルタ処理をおこなう。フィルタ処理は、自動化する。
  3. 原本を保存する。
  4. OCR 用の PDF を作成する。

 上記の方針を踏まえ、以下の対策を実施した。

1.ScanSnap の設定

【読み取りモード】
「画質の選択」:スーパーファイン(300 dpi)
「カラーモード」:カラー
「継続読み取りを有効にする」
【読み取りモードオプション】
「文字列の傾きを自動的に補正します」だけを有効にする

【ファイル形式】
ファイル形式の選択:JPEG(*.jpg)

【ファイルサイズ】
圧縮率: 1 (ファイルサイズ大)


2.フィルタ処理
フィルタ処理には、GIMP を利用する。
フィルタ処理は、Script-Fu 言語とバッチファイルで自動実行スクリプト化する。
フィルタは、閲覧用(カラー)、閲覧用(モノトーン)および OCR 用(二値化)を作成する。


3.原本の保存
取り込んだ JPEG ファイルは、7z 形式で圧縮を行う。


4.PDF ファイルの作成
PDF ファイルは、フィルタ処理後の JPEG ファイルを「サポートしているファイルを Acrobat で結合」などを使用して作成する。
作成後の PDF ファイルは、Adobe Acrobat 「文書」メニュー ー> 「ファイルサイズを縮小」で「互換性を確保」を”Acrobat 9.0 およびそれ以降”を実行して、圧縮する。ここら辺は、Acrobat 7.0 ぐらいがいいのかもしれないが、あえて Acrobat 9.0 としてみる。


以上ざっくり書いたが、次回以降でスクリプトファイルの内容をまとめることにする。

2010年11月13日土曜日

EDIROL FA-66 ドライバのインストールでハマッた

FA-66 ドライバの再インストールをしようとして、ちょっとハマッたので、備忘録として書き残す。

検証環境
  • Windows Vista Business (64ビット) SP2
  • EDIROL FA-66
  • IEEE1394 拡張ボード(Texas Instruments 製チップ)

インストールしたバージョンは、EDIROL FA-66 Driver Ver.1.0.6 for Windows Vista 64ビット版 (2008.03.25 公開)(FA66_Vis64Drv106J.zip)。

再インストールなので、zip ファイルを解凍し、その中の UnInstall.exe を実行する。
再起動後、setup.exe を実行する。手順通りに進め、FA-66 の電源を入れるところまで進める。
ここからが重要。
FA-66 の電源を入れる前に、FA-66 背面の「SAMPLE RATE」スイッチを”44.1”または”48”に設定する。”96”または”192”に設定しておくと、「FA-66 を使用する準備をしています。しばらくお待ちください。」から進まなくなる。場合によっては、強制リセットで再起動してしまう。
マニュアルにも、インストール手順にもそれらしい記述は無いので、環境依存なのか・・・

2010年11月1日月曜日

MeCab を MinGW-w64 でビルド。ついでに、Java バインディングもビルド

MeCab をビルドしたくなった。ついでに Java からも使いたい。そしてそれは、64ビット版でなくてはならない。

環境


MinGW 環境は、これまで色々ビルドしているので、他にも必要なライブラリなどがあるかもしれないが、今回やった手順しか書かないので注意。

libiconv のビルド
tar zxvf libiconv-1.13.1.tar.gz
cd libiconv-1.13.1
./configure --prefix=/mingw --enable-static --host=x86_64-w64-mingw32 --build=x86_64-w64-mingw32
make
make install
libiconv のビルドは、スンナリできた。

MeCab 用 IPA 辞書のビルド
tar zxvf mecab-ipadic-2.7.0-20070801.tar.gz
cd mecab-ipadic-2.7.0-20070801
./configure --prefix=/mingw --with-charset=utf-8
make

MeCab のビルド
パッチファイル(MeCab_0.98_MinGW.patch)については後述する。
以下の手順でビルドすると、 libgcc_s_sjlj-1.dll が実行時に必要となる。このファイルは MinGWディレクトリの ”x86_64-w64-mingw32/bin/”に入っている。パスを通すか、実行ファイルと同じディレクトリにコピーしておく必要がある。依存を無くそうと悶絶してみたが、私のレベルでは、どうしてもできなかった。ちなみに私のレベルは5である。スライムぐらいには勝てる・・・・
tar zxvf /f/Archive/mecab-0.98.tar.gz
cd mecab-0.98
patch -p0 < MeCab_0.98_MinGW.patch
CPPFLAGS='-I/mingw/include' LIBS=-liconv ./configure --prefix=/mingw --host=x86_64-w64-mingw32 --build=x86_64-w64-mingw32 --enable-utf8-only --with-charset=utf8
make

MeCab のパッチファイル
このパッチファイルこそが、今回の血と汗と涙の結晶。若干の説明をおこなう。

【config.h.in】
49 行目
#undef HAVE_PTHREAD_H
↓
//#undef HAVE_PTHREAD_H
これは、pthread を利用しないようにするため。インストールされていなければいらない。うちの環境では、以前 pthread をインストールしていて、それを使うと MeCab.exe 実行して、1行入力後に固まるようなので、利用しないようにした。pthread を利用しない場合は、WindowsAPI の同様の機能を利用するようだ。

【src/feature_index.cpp】
311 行目
case 't':  os_ << (size_t)path->rnode->char_type;     break;
↓
case 't':  os_ << static_cast<unsigned int>((size_t)path->rnode->char_type);     break;
あいまいなキャストだったので、修正。これが正しいのかは神のみぞ知る・・・

【src/libmecab.cpp】
53 行目
#ifdef __cplusplus
↓
#if defined(__cplusplus) && !defined(__MINGW32__)

65 行目
#ifdef __cplusplus
↓
#if defined(__cplusplus) && !defined(__MINGW32__)
ここは、偉大なる先人の知恵。

【src/Makefile.in】
260 行目
INCLUDES = -DDIC_VERSION=$(DIC_VERSION) $(MECAB_WITHOUT_SHARE_DIC) $(MECAB_WITHOUT_MUTEX_LOCK) $(MECAB_USE_UTF8_ONLY) -DMECAB_DEFAULT_RC="\"$(MECAB_DEFAULT_RC)\""
↓
INCLUDES = -DDIC_VERSION=$(DIC_VERSION) $(MECAB_WITHOUT_SHARE_DIC) $(MECAB_WITHOUT_MUTEX_LOCK) $(MECAB_USE_UTF8_ONLY) -DMECAB_DEFAULT_RC='"$(MECAB_DEFAULT_RC)"'
ここの修正は、苦肉の策。どうもうちの環境では、変数の展開に問題があるらしく、思ったようにならない(バックスラッシュだらけになり、バックスラッシュの個数も1個足りないとか・・・)。よって、この値を利用している、src/utils.cpp を修正した。ここは、コンパイルが通るようにしただけで、理想の対処とは程遠い。

【src/mecab.h】
131 行目
#ifdef _WIN32
↓
#if defined(_WIN32) && !defined(__MINGW32__)

254 行目
#ifndef SIWG
↓
#ifndef SWIG
ここも、偉大なる先人の知恵。

【src/utils.cpp】
282 行目
if (rcfile.empty()) rcfile = MECAB_DEFAULT_RC;
↓
if (rcfile.empty()) rcfile = "C:\\appli\\MeCab_utf8\\etc\\mecabrc";
ここは、mecabrc ファイル格納デフォルトディレクトリをハードコーディングした。各自の環境に合わせて、変更する必要がある。
ソースを読むと、ホームディレクトリの”.mecabrc”か、MECABRC 環境変数の値か、レジストリ”HKEY_LOCAL_MACHINE\software\mecab”の”mecabrc”の値か、レジストリ”HKEY_CURRENT_USER\software\mecab”の”mecabrc”の値か、MeCab.dll と同じディレクトリの”mecabrc”を探すらしい。それでも見つからないときにこの値を利用するようだ。
本来的には、Make の変数展開を何とかするべきなんだろうが、LV5 ではなんとも・・・

【src/writer.cpp】
236 行目
case 'L': *os << std::strlen(sentence); break;
↓
case 'L': *os << static_cast<unsigned int>(std::strlen(sentence)); break;
あいまいなキャストってコンパイラに怒られたので、適当にキャストしてみた(おいっ)。いいのかなぁいいのかなぁ。

MeCab Java バインディングのビルド
tar zxvf mecab-java-0.98.tar.gz
cd mecab-java-0.98

先ほどビルドした MeCab から必要なファイルを Java バインディングのディレクトリにコピーする。
cp ../mecab-0.98/src/mecab.h .
cp ../mecab-0.98/src/.libs/libmecab.a .

Java の bin ディレクトリをパスに追加
export PATH=/c/appli/Java/jdk1.6/bin:$PATH

Makefile を修正
TARGET=MeCab
JAVAC=javac
JAVA=java
JAR=jar
CXX=c++
INCLUDE=c:/appli/Java/jdk1.6/include

PACKAGE=org/chasen/mecab

LIBS=-L. -L/mingw/lib -lmecab -liconv
INC=-I$(INCLUDE) -I$(INCLUDE)/win32

all:
    $(CXX) -O3 -c -fpic $(TARGET)_wrap.cxx  $(INC)
    $(CXX) -shared -static $(TARGET)_wrap.o -o $(TARGET).dll $(LIBS)
    $(JAVAC) -encoding utf-8 $(PACKAGE)/*.java
    $(JAVAC) -encoding utf-8 test.java
    $(JAR) cfv $(TARGET).jar $(PACKAGE)/*.class

test:
    LD_LIBRARY_PATH=. MECABRC=./mecabrc $(JAVA) -Djava.file.encoding=euc-jp test

clean:
    rm -fr *.jar *.o *.dll *.class $(PACKAGE)/*.class
    
cleanall:
    rm -fr $(TARGET).java *.cxx


java のコンソール出力が文字化けするので、ファイルにログを吐いておく。、
make > build.log 2>&1

出来上がったもののテストをしてみる。
設定ファイルを作る
cp ../mecab-0.98/mecabrc .

コピーした mecabrc を修正する。辞書ディレクトリを指定する部分に、先ほどビルドした辞書のディレクトリを設定する。
dicdir =  ..\mecab-ipadic-2.7.0-20070801

テストを実施
コンソール出力は、文字化けするのでファイルに吐いて確かめる。
make test > test.log 2>&1

テスト結果
LD_LIBRARY_PATH=. MECABRC=./mecabrc java -Djava.file.encoding=euc-jp test
0.98
太郎 名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
二郎 名詞,固有名詞,人名,名,*,*,二郎,ジロウ,ジロー
に 助詞,格助詞,一般,*,*,*,に,ニ,ニ
この 連体詞,*,*,*,*,*,この,コノ,コノ
本 名詞,一般,*,*,*,*,本,ホン,ホン
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
渡し 動詞,自立,*,*,五段・サ行,連用形,渡す,ワタシ,ワタシ
た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。 記号,句点,*,*,*,*,。,。,。
EOS

 BOS/EOS,*,*,*,*,*,*,*,*
EOS

ちなみに、辞書を指定せずに実行すると、以下のようなログが出力される。
LD_LIBRARY_PATH=. MECABRC=./mecabrc java -Djava.file.encoding=euc-jp test
0.98
Exception in thread "main" java.lang.RuntimeException: tagger.cpp(151) [load_dictionary_resource(param)] param.cpp(71) [ifs] no such file or directory: ./mecabrc
    at org.chasen.mecab.MeCabJNI.new_Tagger__SWIG_1(Native Method)
    at org.chasen.mecab.Tagger.<init>(Tagger.java:128)
    at test.main(test.java:17)
make: *** [test] Error 1

文字コードの違う辞書を指定してしまうと、文字が化け化けになる。指定しているつもりが無くとも、MeCab をインストーラーでインストールしたことがあると、レジストリに設定ファイルの場所が設定されてしまうので、こういう事態に陥りやすいので注意。

MeCab の設定ファイルの指定方法は複数あるが、今回は、環境変数で指定してある。DLL と同じディレクトリに入れる方法もあるのだが、レジストリでの指定より優先順位が低いので、環境によって問題がある。

これでめでたく Java から MeCab を利用することが出来る。
いや、長かった・・・・この手順も長いが、実はかなり遠回りをしてしまい、実際にはここに書かれていない作業を延々としてしまった。GLib コンパイルしたりとか、gettext コンパイルしたりとか・・・
まあ、いろんな意味でレベルは上がった。怪我の功名というやつですな。次は Python バインディングか・・・・