refactor image code to a BlogImage class, test markdown_template functionality
This commit is contained in:
		
							parent
							
								
									65316276dc
								
							
						
					
					
						commit
						15d38a5ceb
					
				
							
								
								
									
										131
									
								
								backblogger.py
								
								
								
								
							
							
						
						
									
										131
									
								
								backblogger.py
								
								
								
								
							|  | @ -3,28 +3,91 @@ import os | |||
| import json | ||||
| from datetime import datetime, date | ||||
| import cv2 | ||||
| from PIL import Image as PILImage | ||||
| from PIL.ExifTags import TAGS | ||||
| 
 | ||||
| def markdown_date(ts): | ||||
|     d = datetime.fromtimestamp(ts) | ||||
|     return f"{d.year}-{d.month}-{d.day}" | ||||
| 
 | ||||
| IMG_WIDTH = 760 | ||||
| def resize_rename(imgpath, article_number, image_number, destpath=None, target_width=IMG_WIDTH): | ||||
|     new_fn = f"article{str(article_number).zfill(2)}_image{str(image_number).zfill(2)}.{imgpath.split('.')[-1]}" | ||||
|     if destpath is None: | ||||
|         destpath = os.path.join('.', imgpath.replace(os.path.basename(imgpath), '')) | ||||
|     dest_fn = os.path.join(destpath, new_fn) | ||||
|     img = cv2.imread(imgpath) | ||||
|     h, w = img.shape[:2] | ||||
|     print(h, w) | ||||
|     if w <= target_width: | ||||
|         cv2.imwrite(dest_fn, img) | ||||
|         return | ||||
|     ratio = target_width / float(w) | ||||
|     new_h = int(h * ratio) | ||||
|     print(ratio, target_width, new_h) | ||||
|     new_img = cv2.resize(img, (target_width, new_h), interpolation=cv2.INTER_AREA) | ||||
|     cv2.imwrite(dest_fn, new_img) | ||||
| class BlogImage: | ||||
|     def __init__(self, filepath, article_number=None, image_number=None): | ||||
|         self.path = filepath | ||||
|         self.article_number = article_number | ||||
|         self.image_number = image_number | ||||
|         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] | ||||
|         if w <= target_width: | ||||
|             cv2.imwrite(dest_fn, img) | ||||
|             return dest_fn | ||||
|         ratio = target_width / float(w) | ||||
|         new_h = int(h * ratio) | ||||
|         new_img = cv2.resize(img, (target_width, new_h), interpolation=cv2.INTER_AREA) | ||||
|         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: | ||||
|     def __init__(self, path): | ||||
|  | @ -32,6 +95,7 @@ class BlogScaffold: | |||
|         self.data = { "images": [], | ||||
|                       "blogfile": None | ||||
|                     } | ||||
|         self.blog_images = [] | ||||
|         # Check the path for backblog metadata | ||||
|         self.scanned = os.path.exists(os.path.join(self.path, "backblog.json")) | ||||
|         if not self.scanned: | ||||
|  | @ -42,12 +106,12 @@ class BlogScaffold: | |||
| 
 | ||||
|     def scan(self): | ||||
|         _, _, files = next(os.walk(self.path)) | ||||
|         self.blog_images = [] | ||||
|         for f in files: | ||||
|             self.data['images'].append({ | ||||
|                 "path": f, | ||||
|                 "date": os.stat(os.path.join(self.path, f)).st_ctime | ||||
|                 }) | ||||
|             if f.split('.')[1].lower() not in ('jpg', 'png', 'bmp', 'jpeg'): continue  | ||||
|             self.blog_images.append(BlogImage(os.path.join(self.path, f))) | ||||
|         self.scanned = True | ||||
|         self.data['images'] = [bi.serialize() for bi in self.blog_images] | ||||
|         self.save() | ||||
|              | ||||
|     def save(self): | ||||
|  | @ -59,23 +123,34 @@ class BlogScaffold: | |||
|             yield datetime.fromtimestamp(i['date']) | ||||
| 
 | ||||
|     def markdown_template(self, article_number): | ||||
|         if not self.scanned: self.scan() | ||||
|         replace = { | ||||
|             "{{TITLE}}": "Backblog basic template about " + self.path, | ||||
|             "{{SLUG}}": os.path.basename(self.path), | ||||
|             "{{CATEGORY}}": "category", | ||||
|             "{{EARLIESTDATE}}": markdown_date(min([i['date'] for i in self.data['images']])), | ||||
|             "{{TODAY}}": str(date.today()), | ||||
|             "{{ARTICLENUM}}": article_number.zfill(2) | ||||
|             "{{ARTICLENUM}}": str(article_number).zfill(2) | ||||
|             } | ||||
|         txt = None | ||||
|         with open("template.md", "r") as f: | ||||
|             txt = f.read() | ||||
|         img_template = txt.split("%%%")[1] | ||||
|         img_txt = "" | ||||
|         for i, image in enumerate(self.data['images']): | ||||
|             img_fn = resize_rename(image['path'], article_number, i) | ||||
|             this_txt = img_template | ||||
|             this_txt.replace("{{IMG_FN}}", img_fn) | ||||
|         for i, image in enumerate(sorted(self.blog_images)): | ||||
|             image.article_number = article_number | ||||
|             image.image_number = i | ||||
|             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): | ||||
|         if not self.scanned: | ||||
|  | @ -86,5 +161,9 @@ if __name__ == '__main__': | |||
|     subdirs = os.listdir('..') | ||||
|     # don't scan program's directory | ||||
|     subdirs.remove(os.path.basename(os.path.abspath('.'))) | ||||
|     #os.chdir('..') | ||||
|     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())}") | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue