Python3|ZIPファイルを作成してロリポップのサーバにFTPするスクリプトを作りました

写真素材note「空気感フォト」を定期的に公開しています。

JPGは3サイズ(L/M/S)あり、3サイズ分のZIPもあるので公開するまで手間がかかります。たとえば、

  1. Lightroom:僕カラーでエディット
  2. Lightroom:サイズごとに指定のファイル名で出力(No40-L-01.jpg/No40-S-06.jpg)
  3. ZIPANG:サイズごとにZIP圧縮
  4. Transmit:サーバにディレクトリを作り、JPGとZIPをアップロード

と画像データを用意するフロー「だけ」でこのくらいあります。さらに公開するまであと数手あります。

でね、単純作業や繰り返し作業は、ITを利用することで効率化が期待できる部分です。ということで、上記3〜4をPythonスクリプトを組んで半自動化することにしました。

動作環境

  • macOS Mojave ver 10.14.15
  • Python 3.7.3

ソースコード

僕得でしかないソースコードがどなたかの参考になれば嬉しい(クリックすると見れます)
""" My note project workflow script.
Create 3 zip files from JPG files.
Upload all JPG/ZIP files to FTPS server.
"""
from ftplib import FTP_TLS
from pathlib import Path
import zipfile


WORK_DIR = '任意の作業ディレクトリ'
FTP_SERVER = 'サーバーURL'
FTP_USER = 'ユーザID'
FTP_PASSWORD = 'パスワード'
UPLOAD_PATH = 'アップロード先ディレクトリ'

p_num = ''  # Project number


def get_file_lists():
    """ Get compressed file lists.

    Returns:
        l_list: L-size jpg file list
        m_list: M
        s_list: S
    """

    path = Path(WORK_DIR)
    l_list, m_list, s_list = [], [], []
    for file_path in path.iterdir():
        if file_path.suffix == '.jpg':
            if 'L' in file_path.name:
                l_list.append(str(file_path.name))
            elif 'M' in file_path.name:
                m_list.append(str(file_path.name))
            else:
                s_list.append(str(file_path.name))

    return l_list, m_list, s_list


def create_zip_files():
    """ Create 3 zip files per L/M/S-size files. """

    # Create targets list
    targets_list = []
    l_list, m_list, s_list = get_file_lists()
    targets_list.append(l_list)
    targets_list.append(m_list)
    targets_list.append(s_list)

    # Get project number from L-size file name
    #   Project number is 2nd & 3rd characters of file name
    #   (ex. No40-L-01.jpg -> it is 40)
    num = l_list[0]
    global p_num
    p_num = num[2:4]

    # Create 3 zip files
    for i, size in enumerate(['L', 'M', 'S']):
        zip_file_path = WORK_DIR + f'No{p_num}-{size}.zip'
        print(f'Creation start: {zip_file_path}')

        with zipfile.ZipFile(zip_file_path, 'w',
                             compression=zipfile.ZIP_DEFLATED) as new_zip:
            for file_name in targets_list[i]:
                new_zip.write(WORK_DIR + f'{file_name}', arcname=file_name)
                print(f'File combining: {file_name}')

        print('_____Creation complated_____')


def ftps_upload_files():
    """ Upload jpg/zip files to FTPS(Passive mode). """

    # Get upload file list(jpg & zip)
    path = Path(WORK_DIR)
    upload_list = [p for p in path.iterdir()
                   if p.suffix == '.jpg' or p.suffix == '.zip']

    try:
        # Login FTP server
        ftps = FTP_TLS(FTP_SERVER)
        ftps.login(FTP_USER, FTP_PASSWORD)
        ftps.prot_p()
        ftps.set_pasv('true')
        print('login OK')

        # Make project number dir
        ftps.cwd(UPLOAD_PATH)
        global p_num
        p_num_dir = 'No' + p_num
        if p_num_dir in ftps.nlst('.'):
            print('This dir already exist.')
        else:
            ftps.mkd(p_num_dir)
            print('Create project dir.')
        ftps.cwd(p_num_dir)
        print(f'Upload directory: {ftps.pwd()}')

        # Upload files
        count = 1
        total_count = len(upload_list)
        for upload_file in upload_list:
            with open(upload_file, 'rb') as f:
                ftps.storbinary('STOR ' + upload_file.name, f)
            print(f'Uploading({count}/{total_count}): {upload_file}')
            count += 1
    except ftps.all_error as e:
        print(e)
        print('_____Upload failed_____')
    else:
        print('_____Upload complated_____')
    finally:
        ftps.quit()
        print('_____Close connection_____')


if __name__ == '__main__':
    create_zip_files()
    ftps_upload_files()

処理をざっくり説明すると

  1. 作業ディレクトリにあるJPGファイルを走査し、L/M/Sサイズごとのパスリストを作る
  2. パスリストから「NoXX-L-01.jpg」の数字部分を取得(5で使う)
  3. パスリストからL/M/SそれぞれのサイズのZIPファイルを作る
  4. FTPサーバにログイン
  5. note用のディレクトリに「NoXX」のディレクトリを作る
  6. JPG、ZIPをアップロードする

PythonはUdemyのこちらの講座で学んでいて、コードやコメントの書き方を真似ています。

今回はユニットテストしていませんが、上記講座でpytestを学んだばかりなので、テストはやってみたいところ。Exceptionの確認だってしてないからね。

学んだことメモ

Pythonでファイル操作(pathlib)

ファイル操作でよく使われるのは「osモジュール」ですが、オブジェクトとしてパスを扱える「pathlibモジュール」を使いました。

例. 指定ディレクトリの拡張子がJPGであるファイルだけのリストを作る

from pathlib import Path

path = Path(任意のディレクトリ)
list = []
for file_path in path.iterdir():
    if file_path.suffix == '.jpg':
        list.append(str(file_path.name))

カレントディレクトリを変えるなど、osモジュールでしかできないこともあるので、用途によって使い分けるのでしょう。

詳しくはこちら

PythonでZIPファイルを作る(zipfile)

with構文でやるのが一般的ですね。

例. リストにあるファイルをhogehoge.zipにまとめる

import zipfile

zip_file_path = WORK_DIR + 'hogehoge.zip'
with zipfile.ZipFile(zip_file_path, 'w', compression=zipfile.ZIP_DEFLATED) as z:
    for file_name in file_list:
        z.write(WORK_DIR + f'{file_name}', arcname=file_name)

zip_file_pathはWORK_DIRを含めて絶対パスにしているので、z.write内で「arcname」にパスを除いたファイル名を指定しています。

詳しくはこちら

PythonでFTPアップロード(ftplib)

あっさりとモジュールがある便利な時代。僕が使っているロリポップに合わせてPASVなどをPythonで書きます。

例. リストにあるファイルをFTPS、PASVモードでアップロード

from ftplib import FTP_TLS

ftps = FTP_TLS(FTP_SERVER)
ftps.login(FTP_USER, FTP_PASSWORD)
ftps.prot_p()
ftps.set_pasv('true')
ftps.cwd(UPLOAD_PATH) # 任意のディレクトリへ
for upload_file in upload_list:
    with open(upload_file, 'rb') as f:
        ftps.storbinary('STOR ' + upload_file.name, f)
ftps.quit()

upload_file.nameはパスの末尾(ファイル名)を指すものです。

詳しくはこちら

おわりに

Udemyでosモジュールを学んだのに、pathlibを使うという未知に踏み出したおかげで学べることがたくさんありました。そのほか、はじめて知ったenumrate関数やリスト内包表記を使ったりもしました。

Pythonって処理を上から下に単純に組むこともできます。でも、Pythonの機能を知っている/知らないではコード量も見栄えも違いますね。

少しでもPythonicなコードが書けていたら嬉しいな。

今回のコードが正解かはわかりません。もっとスマートに書けるかもしれないけど、今の僕が知っている(覚えている)ことで組みました。

このスクリプトには次の写真素材noteから活躍してもらいます。あ〜楽しかった!

【ポートレイト撮影の募集 in 関東(8月10〜25日)】

あなたの行きたい場所に出張してポートレイトやプロフィール写真を撮影します。本期間中の出張エリアは関東圏内に限ります(都内、横浜など)。

★詳細:ポートレイト撮影(Photo Session)
★詳細:イベント撮影(Photo Shooting)

まずはお気軽にご相談ください

この記事を書いた人

花村貴史|Takashi Q. Hanamura

人物や風景、街並み、小物を好んで撮っている空気感フォトグラファー。本職であるPhotographerのかたわら、Engineer、Creatorとしても活動中。
その人・その場の「空気感をそっとすくい撮る」写真が好評。「自分の素敵に気づける写真」を撮る。数年間の心のお稽古によるカウンセラーマインドによって、撮影は「心のセッション」になることも。
オシゴトについては「撮影サービス」からどうぞ。

人物写真|Photo Session

お話しながらのポートレイト撮影です。「今」そして「これから」のあなたにフォーカスして撮影します。SNSやサイト用のプロフィール写真に。

イベント写真|Photo Shooting

「友達と楽しんでいる」「セミナーで講演している」「パーティーを主催している」など、皆さんが大切な時間を過ごしている瞬間をスナップします。

写真術|Photo Lecture

カメラの使い方、撮影術、Lightroomの使い方などの「マンツーマンレクチャ」や「少人数制のワークショップ」について。写真に関する疑問の解決に。