PythonでアーカイブしたZIPファイル名をShift-JISにする

Pythonのzipfileライブラリはファイル名をUTF-8でエンコードするためWindowsのレガシーな解凍ソフトで解凍するとファイル名が文字化けします。ファイル名をShift-JISでエンコードする方法を紹介します。

世の中にはUTF-8で文字化けする解凍ソフトが残っている

2007年9月にZIPの仕様が追加され、エンコードをUTF-8に統一するための仕組みが完成してから、ZIPファイル名はUTF-8にエンコードすることがスタンダードになりつつあります。

APPNOTE - PKZIP/SecureZIP - PKWARE Support Site

しかし、日本国内に目をやると、UTF-8に未対応の解凍ソフト(Explzh +Lhaca Lhaplusなど)が大量に出回っており、使用されています。(WindowsのエクスプローラーだけでUTF-8のZIP解凍できるのに)

ファイル名をShift-JISでエンコードできるのか

各UTF-8未対応の解凍ソフトをアンインストールしてもらうわけにもいかないので、こちらが折れてファイル名をShift-JISでエンコードする方法を考えます。

まず、CPythonのzipfileライブラリのソースコードを見てみます。

python/cpython
The Python programming language. Contribute to python/cpython development by creating an account on GitHub.

CPython3.7では、454行目にファイル名のエンコードの関数があります。抜粋すると下記の通り。

def _encodeFilenameFlags(self):
        try:
            return self.filename.encode('ascii'), self.flag_bits
        except UnicodeEncodeError:
            return self.filename.encode('utf-8'), self.flag_bits | 0x800

あっ・・・まずASCIIでのエンコードを施行して、UnicodeEncodeErrorならば(=ASCIIでなければ)「UTF-8」でエンコードするようにハードコードされていました・・・

標準ライブラリをコピーしてShift-JISに変更する

あまりしたくないですが・・・標準ライブラリをコピーしてShift-JISに対応します。
454行目の'utf-8''cp932'に変更します。また、フラグ0x800を削除します。

ちなみに、0x800は、ZIPの規格で定められた「general purpose bit flag」のBit 11を1にしています(16進数800=2進数 100000000000)。これは、前述のエンコードをUTF-8に統一するための仕組みで、規格では「Language encoding flag (EFS). If this bit is set, the filename and comment fields for this file MUST be encoded using UTF-8.」と定められており、UTF-8でなければ立ててはいけないフラグなので取り除いています。

https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT

zipfile.py

python/cpython
The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
454-: return self.filename.encode('utf-8'), self.flag_bits | 0x800
454+: return self.filename.encode('cp932'), self.flag_bits

import先を今回作ったzipfile.pyに変更します。

from .zipfile import ZipFile
(略)
with ZipFile(zb, mode='w') as zf:
    略

以上の対策で、Windowsのレガシーな解凍ソフトで解凍できるようになります。逆にLinuxなどではUTF-8しか対応していないため文字化けする可能性がありますが未検証です。

コメント