Skip to content

Instantly share code, notes, and snippets.

@IvanaGyro
Last active March 18, 2023 12:15
Show Gist options
  • Select an option

  • Save IvanaGyro/1f1e9647d10de05e84ab5ac339120b18 to your computer and use it in GitHub Desktop.

Select an option

Save IvanaGyro/1f1e9647d10de05e84ab5ac339120b18 to your computer and use it in GitHub Desktop.
Draw datetime on the photos and the videos.
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'])
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