macOSでEMLファイル変換ツールを作成 – tkinterとpy2appで一括処理可能なアプリ開発

はじめに

EMLファイル(電子メールファイル)を読みやすいテキスト形式に変換するツールが必要になりました。市販のソフトを探すより、自分で作った方が早いと判断し、PythonとtkinterでGUIアプリケーションを開発しました。

最終的に、複数ファイルのドラッグ&ドロップ処理、末尾引用文の自動除去、macOS用実行ファイルの作成まで実現できました。開発過程で直面した問題と解決策を共有します。

基本的な機能設計

EMLファイル変換ツールの主要機能は以下の通りです。

  • 複数のEMLファイルを一度に処理(最大100個)
  • ドラッグ&ドロップでファイル選択
  • 件名、差出人、宛先、本文のみを抽出
  • 読みやすいテキスト形式で出力
  • 末尾の引用文を自動除去

tkinterを選択した理由は、Pythonの標準ライブラリで追加インストールが不要だからです。シンプルなGUIには十分な機能を提供します。

出力形式の工夫

変換されたテキストファイルは、以下の形式で保存されます。

============================================================
メール情報
============================================================

件名: 会議資料について
差出人: 田中太郎 <tanaka@example.com>
宛先: 佐藤花子 <sato@example.com>
日付: Mon, 20 May 2025 14:30:15 +0900

------------------------------------------------------------
本文
------------------------------------------------------------

本文の内容がここに表示されます。
Code language: HTML, XML (xml)

メール解析の実装

Pythonの標準ライブラリemailモジュールを使用してEMLファイルを解析しました。EMLファイルは RFC822 形式のテキストファイルで、ヘッダー情報と本文が含まれています。

import email
from email.header import decode_header

# EMLファイルを読み込み
with open(eml_file, 'rb') as f:
    msg = email.message_from_bytes(f.read())

# ヘッダー情報を取得
subject = decode_mime_header(msg.get('Subject', '(件名なし)'))
from_addr = decode_mime_header(msg.get('From', '(差出人不明)'))
to_addr = decode_mime_header(msg.get('To', '(宛先不明)'))
Code language: PHP (php)

文字エンコーディングの処理が重要なポイントです。decode_header関数を使用して、Base64やQuoted-Printableでエンコードされたヘッダーを正しくデコードします。

本文抽出とHTML処理

メールの本文は、プレーンテキストとHTML形式の両方が含まれる場合があります。マルチパート形式のメールから適切に本文を抽出する処理を実装しました。

def extract_body(self, msg):
    body = ""
    
    if msg.is_multipart():
        for part in msg.walk():
            content_type = part.get_content_type()
            
            if content_type == "text/plain":
                payload = part.get_payload(decode=True)
                if payload:
                    charset = part.get_content_charset() or 'utf-8'
                    body = payload.decode(charset)
                    break
            elif content_type == "text/html" and not body:
                # HTMLからテキストを抽出
                payload = part.get_payload(decode=True)
                charset = part.get_content_charset() or 'utf-8'
                html_body = payload.decode(charset)
                body = re.sub(r'<[^>]+>', '', html_body)
    
    return body.strip() if body else "(本文なし)"
Code language: PHP (php)

HTMLメールの場合は、正規表現でタグを除去してプレーンテキストに変換します。これにより、どちらの形式でも統一された読みやすい出力が得られます。

開発過程で気づいたこと

引用文除去機能の実装

メールの返信では、元のメッセージが引用文として含まれます。本文の末尾にある「>」で始まる引用行を自動的に除去する機能を追加しました。

def remove_trailing_quotes(self, text):
    lines = text.split('\n')
    last_non_quote_index = len(lines)
    
    # 後ろから見て、連続する引用行を見つける
    for i in range(len(lines) - 1, -1, -1):
        line = lines[i].strip()
        
        if not line:  # 空行は無視
            continue
        
        if line.startswith('>'):  # 引用行
            continue
        else:
            last_non_quote_index = i + 1
            break
    
    return '\n'.join(lines[:last_non_quote_index])
Code language: PHP (php)

この処理により、新しく書かれた部分のみが抽出され、過去のやり取りに埋もれることなく重要な内容を把握できます。

★ 複数のドラッグ&ドロップでのファイルパスの認識

macOSのドラッグ&ドロップでは、ファイルパスが特殊な形式で渡されます。複数ファイルの場合、以下のような形式になります。

{/path/to/file1.eml} {/path/to/file2.eml}

各ファイルパスが波括弧で囲まれ、スペースで区切られています。空白を含むファイル名でも正確に処理するため、正規表現を使用してパスを抽出しました。

def parse_macos_file_paths(self, file_data):
    import re
    pattern = r'\{([^}]+)\}'
    matches = re.findall(pattern, file_data)
    return matches
Code language: JavaScript (javascript)

この処理により、「My Document.eml」のような空白を含むファイル名でも正しく認識できます。

パフォーマンスと制限

大量のファイル処理を考慮して、最大100個までの制限を設けました。これ以上の処理が必要な場合は、バッチ処理で対応できます。

別スレッドでの処理により、UIが固まることなく進捗状況を表示できます。エラー処理も組み込み、一部のファイルが読み込めなくても処理を継続します。

py2appでの実行ファイル作成

macOS用の実行ファイルを作成するため、py2appを使用しました。setup.pyの設定が重要なポイントです。

OPTIONS = {
    'argv_emulation': False,
    'site_packages': True,
    'plist': {
        'CFBundleName': 'EMLConverter',
        'CFBundleIdentifier': 'jp.chiilabo.emlconverter',
        'NSRequiresAquaSystemAppearance': False,
        'NSHighResolutionCapable': True,
    },
    'includes': [
        'tkinter', 'email', 'email.header',
        're', 'os', 'datetime', 'threading'
    ],
}
Code language: PHP (php)

argv_emulation: Falseの設定により、ドラッグ&ドロップの動作が安定します。また、macOSの現代的な機能との互換性を保つため、以下の設定をメインファイルの先頭に追加しました。

import os
os.environ['tk_nomenus'] = "1"  # tkinterメニューバーを無効化
Code language: PHP (php)

これは古いGUIライブラリと新しいOSの間を橋渡しする設定です。

py2appビルド時の依存関係エラーと解決

ビルドの流れは、まず仮想環境を用意して、ライブラリをインストールし、ビルドします。

配布モード(-Aオプションなし)でのビルド時に、思わぬエラーが発生しました。

実行ファイルを起動すると以下のエラーが発生:

ModuleNotFoundError: No module named 'jaraco.text'Code language: JavaScript (javascript)

ちなみに、このエラーは、dist/EMLConverter.app/Contents/MacOS/EMLConverter のようにターミナルからアプリを直接起動すると表示できます。

このエラーはpkg_resources(setuptoolsの一部)がjaraco.textに依存しているにもかかわらず、py2appがその依存関係を自動検出できなかったことが原因でした。

★ jaraco.textをインストールした

必要な依存関係を手動でインストールすることで解決できました:

pip install jaraco.text jaraco.functools jaraco.contextCode language: CSS (css)

その後、再ビルドを実行:

rm -rf build dist
python setup.py py2appCode language: CSS (css)

テキスト処理ライブラリへの依存

jaraco.textは、Jason R. Coombs氏が開発したテキスト処理ユーティリティライブラリです。setuptoolsの内部で以下のように使用されています:

# pkg_resources/__init__.py内
from jaraco.text import drop_comment, join_continuation, yield_linesCode language: PHP (php)

py2appは静的解析でPythonコードの依存関係を検出しますが、setuptoolsのような複雑なライブラリでは一部の依存関係を見落とすことがあります。

開発モードで見落とされた依存関係を見つける

興味深いことに、開発モード(-Aオプション付き)では同じエラーが発生しませんでした。

py2appには2つのビルドモードがあります。

開発モード(-Aオプション付き)では、シンボリックリンクを使用してファイルサイズを小さく保ちます。Python環境が必要ですが、ビルドが高速でデバッグが容易です。

python setup.py py2app -A
Code language: CSS (css)

配布モード(-Aオプションなし)では、すべての依存関係を含んだスタンドアローンの実行ファイルを作成します。ファイルサイズは大きくなりますが、Pythonがインストールされていない環境でも動作します。

python setup.py py2app
Code language: CSS (css)

開発中は前者を、他の人に配布する際は後者を使用するのが効率的です。

このような違いがあるのは開発モードがシンボリックリンクを使用し、元の仮想環境の依存関係を直接参照するためです。配布モードでは全ての依存関係をアプリケーションバンドルに含める必要があるため、このような隠れた依存関係が問題となります。

実際の開発で得られた知見

tkinterは古いライブラリですが、シンプルなアプリケーションには十分な機能を提供します。外部依存を最小限に抑えることで、配布時の問題を回避できました。

macOSでのドラッグ&ドロップ処理は、プラットフォーム固有の実装が必要です。デバッグ情報を充実させることで、問題の特定と解決が迅速に行えました。

py2appでのビルドは試行錯誤が必要ですが、依存関係の問題も含めて一度設定が決まれば安定して実行ファイルを作成できます。特に配布モードでのビルドでは、開発モードでは発見できない依存関係の問題が表面化することがあります。

まとめ

EMLファイル変換ツールの開発を通じて、Python標準ライブラリの活用、macOS特有の処理、GUI アプリケーションの配布方法について実践的な知識を得ることができました。最終的に、ドラッグ&ドロップ対応、引用文除去、100個までの一括処理が可能なツールが完成し、実用的なアプリケーションとして機能しています。


  1. Python email Module Documentation – EMLファイル解析に使用したPython標準ライブラリの公式ドキュメント
  2. tkinter — Python interface to Tcl/Tk – GUIアプリケーション開発に使用したtkinterライブラリの公式ドキュメント
  3. py2app Official Documentation – macOS用実行ファイル作成ツールpy2appの公式ドキュメント
  4. py2app Tutorial – py2appを使用したアプリケーションビルドの詳細手順
  5. RFC 822: Standard for the Format of ARPA Internet Text Messages – EMLファイルの基となる電子メール形式の公式仕様
  6. Python GUI Programming With Tkinter – Real Python – tkinterを使用したGUIプログラミングの実践的チュートリアル
  7. Email: Examples — Python Documentation – Pythonのemailモジュールを使用した具体的なコード例集
  8. Understanding the RFC-822 format and its relation to EML files – RFC-822形式とEMLファイルの関係についての詳細解説
  9. TkDocs Home – 現代的なTkinterプログラミングの包括的なドキュメントとチュートリアル
  10. py2app GitHub Repository – py2appの最新ソースコードと開発状況