Skip to content

Instantly share code, notes, and snippets.

@YSRKEN
Last active July 7, 2020 17:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save YSRKEN/986fdc6548d59bfe760afb68aeef9dc6 to your computer and use it in GitHub Desktop.
Save YSRKEN/986fdc6548d59bfe760afb68aeef9dc6 to your computer and use it in GitHub Desktop.
画像をTwitter用に変換するやつ。Exifがある場合はなるべく情報をフォトヨドバシっぽく印字してみる。
# Pillowは7.2.0だと駄目で、7.0.0にする必要あり
import os
from pprint import pprint
from typing import Dict, Any
from PIL import Image, ImageDraw, ImageFont
from PIL.ExifTags import TAGS
from PIL.MpoImagePlugin import MpoImageFile
# Exif情報を取得するためのシグネチャ
exif_signature = b'Exif\x00\x00'
# DecompressionBombWarning対策
Image.MAX_IMAGE_PIXELS = 1000000000
if __name__ == '__main__':
# JPEGファイルを入力
path = input('JPEGファイルのパス:')
# 情報の表示位置
print('【表示位置指示】')
print(' 1. 左下')
print(' 2. 右下')
print(' 3. 右上')
print(' 4. 左上')
show_position = input('表示位置(無入力で1):')
if show_position == '':
show_position = '1'
show_position = int(show_position)
if show_position > 4:
show_position = 1
# 回転指示がある場合は別途入力
print('【回転方向指示】')
print(' 1. オリジナル')
print(' 2. 左右反転')
print(' 3. 180度回転')
print(' 4. 上下反転')
print(' 5. 左上原点で縦横入れ替え')
print(' 6. 270度回転')
print(' 7. XY座標でXY入れ替え')
print(' 8. 90度回転')
rotation_command = input('回転方向(0で自動):')
if rotation_command == '':
rotation_command = '0'
rotation_command = int(rotation_command)
if rotation_command > 8:
rotation_command = 0
color_type = input('フォントカラー(0で通常、1で黒っぽい色):')
if color_type == '':
color_type = '0'
color_type = int(color_type)
# 必要な情報を読み取る
with open(path, 'rb') as f:
# Pillowを使い、必要な情報を取得する
im: MpoImageFile = Image.open(path)
temp: Dict[str, Any] = {}
for tag_id, value in im.getexif().items():
temp[TAGS.get(tag_id, tag_id)] = value
if rotation_command != 0:
temp['Orientation'] = rotation_command
if len(temp) != 0:
maker = str(temp['Make']).strip()
model = str(temp['Model']).strip()
time = temp['ExposureTime']
f_number = temp['FNumber']
iso_speed = temp["ISOSpeedRatings"]
if type(time) is not float:
time = 1.0 * time[0] / time[1]
if type(f_number) is not float:
f_number = 1.0 * f_number[0] / f_number[1]
if time < 0.1:
time_str = f'1/{round(1.0 / time)}'
elif time < 1.0:
time_str = f'1/{round(1.0 / time, 1)}'
else:
time_str = f'{round(time, 1)}'
# レンズ情報を取得する
print(f'メーカー:{maker}')
if 'Panasonic' in maker:
lens_name = 'Unknown'
if 'MakerNote' in temp:
raw_data = f.read()
raw_exif_offset = raw_data.find(exif_signature) + len(exif_signature)
maker_note = temp['MakerNote']
ifd_count = int.from_bytes(maker_note[12:14], byteorder='little')
for i in range(0, ifd_count):
pointer = 12 + 2 + i * 12
ifd_data = maker_note[pointer:pointer + 12]
tag_id = int.from_bytes(ifd_data[:2], byteorder='little')
if tag_id == 0x51:
# 取得処理を実施
lens_name_length = int.from_bytes(ifd_data[4:8], byteorder='little')
lens_name_offset = raw_exif_offset + int.from_bytes(ifd_data[8:], byteorder='little')
lens_name_bin = raw_data[lens_name_offset:lens_name_offset + lens_name_length]
lens_name = lens_name_bin.decode('ASCII').replace('\0', '')
elif 'OLYMPUS' in maker:
lens_name = temp['LensModel'].replace('\0', '')
else:
lens_name = 'Unknown'
# 画像がTwitter向けには大きすぎる場合、適宜リサイズする
if im.width > im.height and im.width > 4096:
new_im = im.resize((4096, round(im.height * 4096.0 / im.width)), Image.LANCZOS)
elif im.width < im.height and im.height > 4096:
new_im = im.resize((round(im.width * 4096.0 / im.height), 4096), Image.LANCZOS)
else:
new_im = im.copy()
# 方向転換指示がExif内に存在する場合に対処する
# 参考:https://chiyoh.hatenablog.com/entry/2019/05/02/170448
if 'Orientation' in temp and temp['Orientation'] != 1:
orient = {
1: None,
2: Image.FLIP_LEFT_RIGHT,
3: Image.ROTATE_180,
4: Image.FLIP_TOP_BOTTOM,
5: Image.TRANSPOSE,
6: Image.ROTATE_270,
7: Image.TRANSVERSE,
8: Image.ROTATE_90
}
new_im = new_im.transpose(orient[temp['Orientation']])
# 画像に文字を貼り付ける
image_width = new_im.width
image_height = new_im.height
font_size = round(1.0 * image_height / 72)
inserted_text = f'{maker} {model}, {lens_name}, {time_str}, F{round(f_number, 1)}, ISO{iso_speed}'
font = ImageFont.truetype('ipagp.ttf', size=font_size)
draw = ImageDraw.Draw(new_im)
if show_position == 1:
xy = (font_size, image_height - font_size * 2)
elif show_position == 2:
text_size = font.getsize(inserted_text)
xy = (image_width - font_size - text_size[0], image_height - font_size * 2)
elif show_position == 3:
text_size = font.getsize(inserted_text)
xy = (image_width - font_size - text_size[0], font_size)
elif show_position == 4:
xy = (font_size, font_size)
if color_type == 0:
draw.text(
xy=xy,
text=inserted_text,
fill=(186, 192, 178),
font=font)
else:
draw.text(
xy=xy,
text=inserted_text,
fill=(255-186, 255-192, 255-178),
font=font)
else:
# RGBA画像対策
new_im = Image.new('RGB', im.size, (255, 255, 255))
new_im.paste(im, mask=im.split()[3])
im = new_im
# 画像がTwitter向けには大きすぎる場合、適宜リサイズする
if im.width > im.height and im.width > 4096:
new_im = im.resize((4096, round(im.height * 4096.0 / im.width)), Image.LANCZOS)
elif im.width < im.height and im.height > 4096:
new_im = im.resize((round(im.width * 4096.0 / im.height), 4096), Image.LANCZOS)
else:
new_im = im.copy()
# ファイルサイズが5MB未満かつ画素数[B]未満になるように調整する
path_dir = os.path.dirname(path)
path_file_name = os.path.splitext(os.path.basename(path))[0]
path_file_ext = os.path.splitext(path)[1]
if path_file_ext != '.jpg':
path_file_ext = '.jpg'
for quality in reversed(range(101)):
new_path = os.path.join(path_dir, path_file_name + f'_q{quality}' + path_file_ext)
with open(new_path, 'wb') as f2:
new_im.save(f2, quality=quality, subsampling='4:4:4')
file_stat = os.stat(new_path)
print(f'品質{quality}:{file_stat.st_size}バイト')
if file_stat.st_size < 5 * 10 ** 6 and file_stat.st_size < new_im.width * new_im.height:
break
os.remove(new_path)
print('完了.\n')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment