Last active
March 18, 2023 12:15
-
-
Save IvanaGyro/1f1e9647d10de05e84ab5ac339120b18 to your computer and use it in GitHub Desktop.
Draw datetime on the photos and the videos.
This file contains hidden or 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
| import os | |
| import io | |
| from pathlib import Path | |
| from PIL import Image | |
| from PIL import ImageFont | |
| from PIL import ImageDraw | |
| from PIL import ExifTags | |
| import exiftool | |
| import piexif | |
| (Path(__file__).parent / 'out').mkdir(parents=True, exist_ok=True) | |
| def flip_horizontal(im): | |
| return im.transpose(Image.FLIP_LEFT_RIGHT) | |
| def flip_vertical(im): | |
| return im.transpose(Image.FLIP_TOP_BOTTOM) | |
| def rotate_180(im): | |
| return im.transpose(Image.ROTATE_180) | |
| def rotate_90(im): | |
| return im.transpose(Image.ROTATE_90) | |
| def rotate_270(im): | |
| return im.transpose(Image.ROTATE_270) | |
| def transpose(im): | |
| return rotate_90(flip_horizontal(im)) | |
| def transverse(im): | |
| return rotate_90(flip_vertical(im)) | |
| orientation_funcs = [ | |
| None, | |
| lambda x: x, | |
| flip_horizontal, | |
| rotate_180, | |
| flip_vertical, | |
| transpose, | |
| rotate_270, | |
| transverse, | |
| rotate_90, | |
| ] | |
| for filename in os.listdir('.'): | |
| if not filename.endswith('.jpg'): | |
| continue | |
| if filename != __file__ and filename != '.DS_Store' and Path( | |
| filename).is_file(): | |
| img = Image.open(filename) | |
| exif = piexif.load(filename) | |
| # rotate jpeg | |
| if img.format == 'JPEG': | |
| zeroth_ifd = exif.get('0th', {}) | |
| orientation = zeroth_ifd.get(piexif.ImageIFD.Orientation, 1) | |
| img = orientation_funcs[orientation](img) | |
| zeroth_ifd[piexif.ImageIFD.Orientation] = 1 | |
| exif.get('1st', {})[piexif.ImageIFD.Orientation] = 1 | |
| img.info['exif'] = piexif.dump(exif) | |
| img.format = 'JPEG' | |
| datetime = '' | |
| with exiftool.ExifTool() as et: | |
| metadata = et.get_metadata(filename) | |
| try: | |
| datetime = metadata['EXIF:DateTimeOriginal'] | |
| datetime_parts = datetime.split() | |
| datetime = f'{datetime_parts[0].replace(":", "-")} {datetime_parts[1]}' | |
| except KeyError: | |
| print(filename, metadata) | |
| continue | |
| draw = ImageDraw.Draw(img) | |
| # font = ImageFont.truetype(<font-file>, <font-size>) | |
| w, h = img.size | |
| font_size = int((w**2 + h**2)**0.5 // 40) | |
| font = ImageFont.truetype('TaipeiSansTCBeta-Bold.ttf', font_size) | |
| # draw.text((x, y),"Sample Text",(r,g,b)) | |
| draw.text((w - int(font_size * 10), h - int(font_size * 1.2)), | |
| datetime, (255, 0, 0), | |
| font=font) | |
| # create new thumbnail | |
| if 'thumbnail' in exif: | |
| try: | |
| thumbnail = Image.open(io.BytesIO(exif['thumbnail'])) | |
| thumbnail_size = thumbnail.size | |
| thumbnail = img.copy() | |
| thumbnail.thumbnail(thumbnail_size, Image.LANCZOS) | |
| thumbnail_bytes = io.BytesIO() | |
| thumbnail.save(thumbnail_bytes, format=img.format) | |
| exif['thumbnail'] = thumbnail_bytes.getvalue() | |
| img.info['exif'] = piexif.dump(exif) | |
| except Exception as e: | |
| print(filename, e) | |
| img.save(f'out/{filename}', exif=img.info['exif']) |
This file contains hidden or 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
| import subprocess | |
| import re | |
| import datetime | |
| import time | |
| import pytz | |
| import os | |
| def get_create_time(filename): | |
| p = subprocess.Popen( | |
| ['exiftool', filename, '-CreateDate'], | |
| stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, | |
| ) | |
| out, err = p.communicate() | |
| if err: | |
| print(err) | |
| exit(1) | |
| m = re.search(r'Create Date[ ]*:[ ]*(\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2})', out) | |
| if m is None: | |
| return None | |
| print('create date not found') | |
| # convert UTC time to timestamp | |
| create_time = datetime.datetime.strptime(m.group(1), '%Y:%m:%d %H:%M:%S') | |
| create_time = create_time.replace(tzinfo=pytz.timezone('UTC')) | |
| create_time = create_time.astimezone() | |
| create_time = time.mktime(create_time.timetuple()) | |
| return create_time | |
| def draw_timestamp( | |
| filename, | |
| output_filename, | |
| font_path, | |
| font_size='(w^2+h^2)^0.5/40', | |
| font_color='red', | |
| ): | |
| x = 'w-tw-20' | |
| y = 'h-lh-20' | |
| start_time = get_create_time(filename) | |
| p = subprocess.Popen([ | |
| 'ffmpeg', | |
| '-i', filename, | |
| '-vf', f"scale='iw*1920/max(iw,ih)':-1, drawtext=fontfile={font_path}: fontsize={font_size}: text='%{{pts\\:localtime\\:{start_time}}}': x={x}: y={y}: fontcolor={font_color}: box=0", | |
| '-c:v', 'libx264', | |
| '-preset', 'ultrafast', | |
| output_filename, | |
| ]) | |
| p.wait() | |
| def compress(filename, output_filename, target_size): | |
| p = subprocess.Popen([ | |
| 'ffprobe', | |
| '-v', 'error', | |
| '-show_entries', 'stream=bit_rate:format=duration', | |
| '-select_streams', 'a', | |
| '-of', 'default=noprint_wrappers=1:nokey=1', | |
| filename, | |
| ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) | |
| out, err = p.communicate() | |
| if err: | |
| print(err) | |
| return | |
| audio_bit_rate, duration, *_ = out.split('\n') | |
| audio_bit_rate, duration = (float(e) for e in (audio_bit_rate, duration)) | |
| target_video_bit_rate = (target_size - audio_bit_rate * duration) / duration | |
| tmp_filename = filename + 'tmp.mp4' | |
| pass_log_prefix = filename + 'pass' | |
| p = subprocess.Popen([ | |
| 'ffmpeg', | |
| '-y', | |
| '-i', str(filename), | |
| '-c:v', 'libx264', | |
| '-preset', 'medium', | |
| '-b:v', str(target_video_bit_rate), | |
| '-pass', '1', | |
| '-passlogfile', pass_log_prefix, | |
| '-acodec', 'copy', | |
| '-f', 'mp4', | |
| tmp_filename, | |
| ]) | |
| p.wait() | |
| p = subprocess.Popen([ | |
| 'ffmpeg', | |
| '-i', str(filename), | |
| '-c:v', 'libx264', | |
| '-preset', 'medium', | |
| '-b:v', str(target_video_bit_rate), | |
| '-pass', '2', | |
| '-passlogfile', pass_log_prefix, | |
| '-acodec', 'copy', | |
| output_filename, | |
| ]) | |
| p.wait() | |
| os.remove(tmp_filename) | |
| os.remove(f'{pass_log_prefix}-0.log') | |
| os.remove(f'{pass_log_prefix}-0.log.mbtree') | |
| FONT_PATH = './TaipeiSansTCBeta-Regular.ttf' | |
| for filename in os.listdir('.'): | |
| if not filename.endswith('.mp4'): | |
| continue | |
| drawn_file = filename + '.drawn.mp4' | |
| draw_timestamp(filename, drawn_file, FONT_PATH) | |
| compress(drawn_file, f'out/{filename}', 28 * 8 * 1024 * 1024) | |
| os.remove(drawn_file) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment