import boto3 import io import jinja2 import PIL.Image import sys import urllib.parse import etiquette import r2_credentials from voussoirkit import dotdict from voussoirkit import imagetools from voussoirkit import pathclass from voussoirkit import spinal from voussoirkit import vlogging log = vlogging.get_logger(__name__, 'photography_generate') pdb = etiquette.photodb.PhotoDB('D:\\Documents\\Photos\\_etiquette') s3 = boto3.resource('s3', endpoint_url = f'https://{r2_credentials.get_account_id()}.r2.cloudflarestorage.com', aws_access_key_id = r2_credentials.get_access_key(), aws_secret_access_key = r2_credentials.get_access_secret(), ) bucket = s3.Bucket('voussoir') S3_EXISTING_FILES = set(item.key for item in bucket.objects.filter(Prefix="photography/")) PUBLISH_TAGNAME = 'voussoir_net_publish' HEADLINER_TAGNAME = 'voussoir_net_headliner' PHOTOGRAPHY_ROOTDIR = pathclass.Path(__file__).parent ATOM_FILE = PHOTOGRAPHY_ROOTDIR.with_child('photography.atom') DOMAIN_ROOTDIR = PHOTOGRAPHY_ROOTDIR.parent DOMAIN_WEBROOT = 'https://voussoir.net' PHOTOGRAPHY_WEBROOT = 'https://voussoir.net/photography' S3_WEBROOT = 'https://files.voussoir.net' SIZE_SMALL = 1440 SIZE_TINY = 360 def webpath(path, anchor=None): path = path.relative_to(DOMAIN_ROOTDIR, simple=True).replace('\\', '/').lstrip('/') path = DOMAIN_WEBROOT.rstrip('/') + '/' + path if anchor is not None: path += '#' + anchor.lstrip('#') return path class Photo: def __init__(self, etq_photo, etq_album=None): self.etq_photo = etq_photo self.article_id = self.etq_photo.real_path.replace_extension('').basename if etq_album is None: parent_key = 'photography' else: parent_key = f'photography/{etq_album.title}' self.s3_key = f'{parent_key}/{self.etq_photo.real_path.basename}' self.small_key = f'{parent_key}/small_{self.etq_photo.real_path.basename}' self.tiny_key = f'{parent_key}/tiny_{self.etq_photo.real_path.basename}' self.color_class = 'monochrome' if self.etq_photo.has_tag('monochrome') else '' self.s3_exists = self.s3_key in S3_EXISTING_FILES; self.img_url = f'{S3_WEBROOT}/{self.s3_key}' self.small_url = f'{S3_WEBROOT}/{self.small_key}' self.tiny_url = f'{S3_WEBROOT}/{self.tiny_key}' self.anchor_url = f'{DOMAIN_WEBROOT}/{parent_key}#{self.article_id}' self.published = imagetools.get_exif_datetime(self.etq_photo.real_path) def prepare(self): if not self.s3_exists: self.s3_upload() def make_thumbnail(self, size): image = PIL.Image.open(self.etq_photo.real_path.absolute_path) icc = image.info.get('icc_profile') (image_width, image_height) = image.size exif = image.getexif() (width, height) = imagetools.fit_into_bounds(image_width, image_height, size, size) image = image.resize((width, height), PIL.Image.LANCZOS) bio = io.BytesIO() image.save(bio, format='jpeg', quality=75, exif=exif, icc_profile=icc) bio.seek(0) return bio def s3_upload(self): log.info('Uploading %s as %s', self.etq_photo.real_path.absolute_path, self.s3_key) bucket.upload_fileobj(self.make_thumbnail(SIZE_SMALL), self.small_key) bucket.upload_fileobj(self.make_thumbnail(SIZE_TINY), self.tiny_key) bucket.upload_fileobj(self.etq_photo.real_path.open('rb'), self.s3_key) self.s3_exists = True def render_web(self, index=None, totalcount=None): if totalcount is not None: number_tag = f'#{index}/{totalcount}' else: number_tag = '' return f'''
{number_tag}
''' def render_atom(self): return f''' {self.article_id} {self.article_id} {self.published.isoformat()} ]]> ''' class Album: def __init__(self, etq_album): self.etq_album = etq_album self.article_id = self.etq_album.title self.photos = list(self.etq_album.get_photos()) self.photos = [p for p in self.photos if p.has_tag(PUBLISH_TAGNAME)] self.photos = [Photo(etq_photo=photo, etq_album=self.etq_album) for photo in self.photos] self.photos.sort(key=lambda p: p.published) # self.link = webpath(path) self.web_url = f'{PHOTOGRAPHY_WEBROOT}/{self.article_id}' self.published = self.photos[0].published def prepare(self): for photo in self.photos: photo.prepare() def render_web(self, index=None, totalcount=None): headliners = [p for p in self.photos if p.etq_photo.has_tag(HEADLINER_TAGNAME)] return jinja2.Template('''

{{article_id}}

{% for photo in headliners %} {{photo.render_web()}} {% endfor %}
{% for photo in photos %} {% endfor %}
''').render( article_id=self.article_id, web_url=self.web_url, photos=self.photos, headliners=headliners, ) def render_atom(self): photos = [] for photo in self.photos: line = f'
'.replace('\\', '/') photos.append(line) photos = '\n'.join(photos) return f''' {self.article_id} {self.article_id} {self.published.isoformat()} ''' def write(path, content): ''' open() and write the file, with validation that it is in the writing dir. ''' path = pathclass.Path(path) if path not in PHOTOGRAPHY_ROOTDIR: raise ValueError(path) print(path.absolute_path) path.write('w', content, encoding='utf-8') def make_webpage(items, is_root, doctitle): rss_link = f'{PHOTOGRAPHY_WEBROOT}/{ATOM_FILE.basename}' if is_root else None back_link = None if is_root else PHOTOGRAPHY_WEBROOT sort_reverse = is_root html = jinja2.Template(''' {% if rss_link %} {% endif %} {{doctitle}}
hint: /
scrollbar on/off {% if rss_link %} Atom {% endif %} {%- if back_link -%} Back {%- endif -%}
{% if not is_root %}

{{doctitle}}

{% endif %} {% for item in items %} {{item.render_web(index=loop.index, totalcount=none if is_root else (items|length))}} {% endfor %}

Ethan Dalool

Contact me: photography@voussoir.net

''').render( is_root=is_root, doctitle=doctitle, rss_link=rss_link, back_link=back_link, items=items, ) return html def write_atom(items): atom = jinja2.Template(''' voussoir.net/photography voussoir.net/photography {% for item in items %} {{item.render_atom()}} {% endfor %} '''.strip()).render(items=items) write(ATOM_FILE, atom) # write_directory_index(PHOTOGRAPHY_ROOTDIR) # for directory in PHOTOGRAPHY_ROOTDIR.walk_directories(): # write_directory_index(directory) @vlogging.main_decorator def main(argv): singlephotos = list(pdb.search(tag_mays=[PUBLISH_TAGNAME], has_albums=False, yield_albums=False, yield_photos=True).results) singlephotos += list(pdb.search(tag_mays=['voussoir_net_publish_single'], yield_albums=False, yield_photos=True).results) singlephotos = [Photo(p) for p in singlephotos] singlephotos.sort(key=lambda i: i.published, reverse=True) albums = list(pdb.search(tag_musts=[PUBLISH_TAGNAME], tag_forbids=['voussoir_net_publish_single'], has_albums=True, yield_albums=True, yield_photos=False).results) albums = [Album(a) for a in albums] albums.sort(key=lambda i: i.published, reverse=True) items = singlephotos + albums items.sort(key=lambda i: i.published, reverse=True) for item in items: item.prepare() log.info('Writing homepage') homepage_html = make_webpage(items, is_root=True, doctitle='photography') homepage_file = PHOTOGRAPHY_ROOTDIR.with_child('photography.html') homepage_file.write('w', homepage_html) for album in albums: album_html = make_webpage(album.photos, is_root=False, doctitle=album.article_id) album_file = PHOTOGRAPHY_ROOTDIR.with_child(album.article_id).replace_extension('html') log.info('Writing %s', album_file.absolute_path) album_file.write('w', album_html) write_atom(items) return 0 if __name__ == '__main__': raise SystemExit(main(sys.argv[1:]))