refactor image code to a BlogImage class, test markdown_template functionality

This commit is contained in:
John McCardle 2022-01-22 22:50:01 -05:00
parent 65316276dc
commit 15d38a5ceb
1 changed files with 105 additions and 26 deletions

View File

@ -3,28 +3,91 @@ import os
import json import json
from datetime import datetime, date from datetime import datetime, date
import cv2 import cv2
from PIL import Image as PILImage
from PIL.ExifTags import TAGS
def markdown_date(ts): def markdown_date(ts):
d = datetime.fromtimestamp(ts) d = datetime.fromtimestamp(ts)
return f"{d.year}-{d.month}-{d.day}" return f"{d.year}-{d.month}-{d.day}"
IMG_WIDTH = 760 IMG_WIDTH = 760
def resize_rename(imgpath, article_number, image_number, destpath=None, target_width=IMG_WIDTH): class BlogImage:
new_fn = f"article{str(article_number).zfill(2)}_image{str(image_number).zfill(2)}.{imgpath.split('.')[-1]}" def __init__(self, filepath, article_number=None, image_number=None):
if destpath is None: self.path = filepath
destpath = os.path.join('.', imgpath.replace(os.path.basename(imgpath), '')) self.article_number = article_number
dest_fn = os.path.join(destpath, new_fn) self.image_number = image_number
img = cv2.imread(imgpath) self._metadata = None
self.resized = False
self.file_date = os.stat(self.path).st_ctime if self.path else None
def metadata(self, force_reload=False):
if self._metadata and not force_reload: return self._metadata
img = PILImage.open(self.path)
exif = img.getexif()
self._metadata = {TAGS.get(t, t): exif[t] for t in exif}
return self._metadata
def blog_image_name(self):
AN = str(self.article_number).zfill(2)
IN = str(self.image_number).zfill(2)
EXT = os.path.basename(self.path).split('.')[1]
return f"article{AN}_image{IN}.{EXT}"
def resize(self, target_width, dest_fn=None):
if dest_fn is None: dest_fn = self.blog_image_name()
img = cv2.imread(self.path)
h, w = img.shape[:2] h, w = img.shape[:2]
print(h, w)
if w <= target_width: if w <= target_width:
cv2.imwrite(dest_fn, img) cv2.imwrite(dest_fn, img)
return return dest_fn
ratio = target_width / float(w) ratio = target_width / float(w)
new_h = int(h * ratio) new_h = int(h * ratio)
print(ratio, target_width, new_h)
new_img = cv2.resize(img, (target_width, new_h), interpolation=cv2.INTER_AREA) new_img = cv2.resize(img, (target_width, new_h), interpolation=cv2.INTER_AREA)
cv2.imwrite(dest_fn, new_img) cv2.imwrite(dest_fn, new_img)
self.resized = True
def markdown_template(self, template):
img_replace = {
"{{IMG_FN}}": self.blog_image_name(),
"{{IMG_ORIG_FN}}": os.path.basename(self.path),
"{{IMG_FILE_DATE}}": markdown_date(self.file_date),
"{{IMG_META_DATE}}": self.metadata().get('DateTime', 'No Metadata')
}
for k in img_replace:
template = template.replace(k, img_replace[k])
return template
def serialize(self):
return {
"path": os.path.basename(self.path),
"metadata": self._metadata,
"resized": self.resized,
"resize_path": self.blog_image_name() if self.resized else '',
"date": self.file_date
}
def deserialize(self, data, directory):
self.path = os.path.join(directory, data['path'])
self._metadata = data['metadata']
self.resized = data['resized']
if data['date']: os.stat(self.path).st_ctime if self.path else None
def __repr__(self):
return f"<BlogImage path={os.path.basename(self.path)}, article_number={self.article_number}, image_number={self.image_number}>"
def best_date(self):
meta_date = self.metadata().get('DateTime')
if meta_date:
try:
return datetime.strptime(meta_date, '%Y:%m:%d %H:%M:%S').timestamp()
except:
print(f'DateTime exif failed parsing: {meta_date} ({self.path})')
if self.file_date:
return self.file_date
return -1
def __lt__(self, other):
return self.best_date() < other.best_date()
class BlogScaffold: class BlogScaffold:
def __init__(self, path): def __init__(self, path):
@ -32,6 +95,7 @@ class BlogScaffold:
self.data = { "images": [], self.data = { "images": [],
"blogfile": None "blogfile": None
} }
self.blog_images = []
# Check the path for backblog metadata # Check the path for backblog metadata
self.scanned = os.path.exists(os.path.join(self.path, "backblog.json")) self.scanned = os.path.exists(os.path.join(self.path, "backblog.json"))
if not self.scanned: if not self.scanned:
@ -42,12 +106,12 @@ class BlogScaffold:
def scan(self): def scan(self):
_, _, files = next(os.walk(self.path)) _, _, files = next(os.walk(self.path))
self.blog_images = []
for f in files: for f in files:
self.data['images'].append({ if f.split('.')[1].lower() not in ('jpg', 'png', 'bmp', 'jpeg'): continue
"path": f, self.blog_images.append(BlogImage(os.path.join(self.path, f)))
"date": os.stat(os.path.join(self.path, f)).st_ctime
})
self.scanned = True self.scanned = True
self.data['images'] = [bi.serialize() for bi in self.blog_images]
self.save() self.save()
def save(self): def save(self):
@ -59,23 +123,34 @@ class BlogScaffold:
yield datetime.fromtimestamp(i['date']) yield datetime.fromtimestamp(i['date'])
def markdown_template(self, article_number): def markdown_template(self, article_number):
if not self.scanned: self.scan()
replace = { replace = {
"{{TITLE}}": "Backblog basic template about " + self.path, "{{TITLE}}": "Backblog basic template about " + self.path,
"{{SLUG}}": os.path.basename(self.path), "{{SLUG}}": os.path.basename(self.path),
"{{CATEGORY}}": "category", "{{CATEGORY}}": "category",
"{{EARLIESTDATE}}": markdown_date(min([i['date'] for i in self.data['images']])), "{{EARLIESTDATE}}": markdown_date(min([i['date'] for i in self.data['images']])),
"{{TODAY}}": str(date.today()), "{{TODAY}}": str(date.today()),
"{{ARTICLENUM}}": article_number.zfill(2) "{{ARTICLENUM}}": str(article_number).zfill(2)
} }
txt = None txt = None
with open("template.md", "r") as f: with open("template.md", "r") as f:
txt = f.read() txt = f.read()
img_template = txt.split("%%%")[1] img_template = txt.split("%%%")[1]
img_txt = "" img_txt = ""
for i, image in enumerate(self.data['images']): for i, image in enumerate(sorted(self.blog_images)):
img_fn = resize_rename(image['path'], article_number, i) image.article_number = article_number
this_txt = img_template image.image_number = i
this_txt.replace("{{IMG_FN}}", img_fn) image.resize(IMG_WIDTH)
img_txt += image.markdown_template(img_template)
txt = txt.split("%%%")[0] + img_txt + txt.split("%%%")[2]
for k in replace:
txt = txt.replace(k, replace[k])
template_fn = f"{str(article_number).zfill(2)}_{os.path.basename(self.path)}.md"
render_template_fn = os.path.join(self.path, template_fn)
with open(render_template_fn, "w") as f:
f.write(txt)
self.data['blogfile'] = template_fn
def __repr__(self): def __repr__(self):
if not self.scanned: if not self.scanned:
@ -86,5 +161,9 @@ if __name__ == '__main__':
subdirs = os.listdir('..') subdirs = os.listdir('..')
# don't scan program's directory # don't scan program's directory
subdirs.remove(os.path.basename(os.path.abspath('.'))) subdirs.remove(os.path.basename(os.path.abspath('.')))
#os.chdir('..')
scaffolds = [BlogScaffold(os.path.join('..', sd)) for sd in subdirs] scaffolds = [BlogScaffold(os.path.join('..', sd)) for sd in subdirs]
scaffolds.sort(key = lambda s: s.blog_images[-1], reverse=True)
for s in scaffolds:
print(f"{os.path.basename(s.path)} - earliest {markdown_date(s.blog_images[0].best_date())} - latest {markdown_date(s.blog_images[-1].best_date())}")