Last active
July 7, 2020 17:26
-
-
Save YSRKEN/986fdc6548d59bfe760afb68aeef9dc6 to your computer and use it in GitHub Desktop.
画像をTwitter用に変換するやつ。Exifがある場合はなるべく情報をフォトヨドバシっぽく印字してみる。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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