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

はじめに

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

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

2007 年 9 月に ZIP の仕様が追加され、エンコードを UTF-8 に統一するための仕組みが完成してから、ZIP ファイル名は UTF-8 にエンコードすることがスタンダードになりつつあります。
APPNOTE - PKZIP & SecureZIP
https://support.pkware.com/home/pkzip/developer-tools/appnote
しかし、日本国内では、UTF-8 に未対応の解凍ソフト(+Lhaca Lhaplus など)を使っているユーザーが多くいます。(Windows のエクスプローラーだけで UTF-8 の ZIP 解凍できるのに)

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

全ユーザーを説得して各 UTF-8 未対応の解凍ソフトをアンインストールして回るわけにもいかないので、ファイル名を Shift-JIS でエンコードする方法を考えます。
まず、CPython の zipfile ライブラリのソースコードを見てみます。
cpython/zipfile.py at 3.7 · python/cpython - GitHub
https://github.com/python/cpython/blob/3.7/Lib/zipfile.py#L454
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 でなければ立ててはいけないフラグなので取り除いています。
APPNOTE.TXT - .ZIP File Format Specification
https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
zipfile.py
cpython/zipfile.py at 3.7 · python/cpython - GitHub
https://github.com/python/cpython/blob/3.7/Lib/zipfile.py#L454
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 しか対応していない場合文字化けする可能性があります。