Update /photography.
This commit is contained in:
parent
42d498f3f7
commit
1c40e761f7
2 changed files with 541 additions and 458 deletions
|
@ -1,330 +0,0 @@
|
|||
:root
|
||||
{
|
||||
--color_bodybg: #272822;
|
||||
--color_codebg: rgba(255, 255, 255, 0.05);
|
||||
--color_codeborder: rgba(255, 255, 255, 0.2);
|
||||
--color_h1bg: #284142;
|
||||
--color_htmlbg: #1b1c18;
|
||||
--color_blockquotebg: rgba(0, 0, 0, 0.2);
|
||||
--color_blockquoteedge: rgba(255, 255, 255, 0.2);
|
||||
--color_inlinecodebg: rgba(255, 255, 255, 0.1);
|
||||
--color_link: #ae81ff;
|
||||
--color_maintext: #ddd;
|
||||
}
|
||||
|
||||
*, *:before, *:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
html
|
||||
{
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
|
||||
background-color: var(--color_htmlbg);
|
||||
color: var(--color_maintext);
|
||||
|
||||
font-family: Verdana, sans-serif;
|
||||
font-size: 10pt;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
min-height: 100%;
|
||||
width: fit-content;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding: 8px;
|
||||
}
|
||||
body.noscrollbar::-webkit-scrollbar
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
body.noscrollbar
|
||||
{
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
header
|
||||
{
|
||||
width: 100%;
|
||||
max-width: 120em;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: end;
|
||||
}
|
||||
header > *
|
||||
{
|
||||
display: inline-block;
|
||||
padding: 16px;
|
||||
background-color: var(--color_bodybg);
|
||||
}
|
||||
|
||||
.album,
|
||||
.photograph
|
||||
{
|
||||
position: relative;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 8vh;
|
||||
margin-bottom: 8vh;
|
||||
}
|
||||
|
||||
.photograph
|
||||
{
|
||||
padding: 2vh;
|
||||
background-color: var(--color_bodybg);
|
||||
border-radius: 16px;
|
||||
}
|
||||
article .photograph:first-of-type
|
||||
{
|
||||
margin-top: 0;
|
||||
}
|
||||
article .photograph:last-of-type
|
||||
{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.photograph img
|
||||
{
|
||||
max-height: 92vh;
|
||||
border-radius: 8px;
|
||||
}
|
||||
article .morelink
|
||||
{
|
||||
font-size: 2em;
|
||||
text-align: center;
|
||||
margin-top: 0;
|
||||
}
|
||||
@media not print
|
||||
{
|
||||
.photograph
|
||||
{
|
||||
box-shadow: #000 0px 0px 40px -10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px)
|
||||
{
|
||||
article
|
||||
{
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px)
|
||||
{
|
||||
.photograph
|
||||
{
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media not all and (pointer: fine)
|
||||
{
|
||||
#keyboardhint,
|
||||
#scrollbartoggle
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5
|
||||
{
|
||||
margin-bottom: 0;
|
||||
padding: 8px;
|
||||
}
|
||||
h1:first-child, h2:first-child, h3:first-child, h4:first-child, h5:first-child
|
||||
{
|
||||
margin-top: 0;
|
||||
}
|
||||
h2, h3, h4, h5
|
||||
{
|
||||
border-bottom: 1px solid var(--color_maintext);
|
||||
/*background-color: var(--color_h1bg);*/
|
||||
}
|
||||
h1 + p
|
||||
{
|
||||
margin-top: 0;
|
||||
}
|
||||
p:last-child
|
||||
{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h1 {font-size: 2.00em;} h1 * {font-size: inherit;}
|
||||
h2 {font-size: 1.75em;} h2 * {font-size: inherit;}
|
||||
h3 {font-size: 1.50em;} h3 * {font-size: inherit;}
|
||||
h4 {font-size: 1.25em;} h4 * {font-size: inherit;}
|
||||
h5 {font-size: 1.00em;} h5 * {font-size: inherit;}
|
||||
|
||||
.header_anchor_link {display: none; font-size: 1.0em; text-decoration: none}
|
||||
h1:hover > .header_anchor_link {display: initial;}
|
||||
h2:hover > .header_anchor_link {display: initial;}
|
||||
h3:hover > .header_anchor_link {display: initial;}
|
||||
h4:hover > .header_anchor_link {display: initial;}
|
||||
h5:hover > .header_anchor_link {display: initial;}
|
||||
|
||||
a
|
||||
{
|
||||
color: var(--color_link);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
article *
|
||||
{
|
||||
max-width: 100%;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#table_of_contents
|
||||
{
|
||||
border: 1px solid var(--color_blockquoteedge);
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
blockquote
|
||||
{
|
||||
background-color: var(--color_blockquotebg);
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: 0;
|
||||
border-left: 4px solid var(--color_blockquoteedge);
|
||||
|
||||
padding: 8px;
|
||||
padding-inline-start: 20px;
|
||||
padding-inline-end: 20px;
|
||||
}
|
||||
|
||||
table
|
||||
{
|
||||
border-collapse: collapse;
|
||||
font-size: 1em;
|
||||
}
|
||||
table, table th, table td
|
||||
{
|
||||
border: 1px solid var(--color_maintext);
|
||||
}
|
||||
table th, table td
|
||||
{
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
ol ol, ul ul, ol ul, ul ol
|
||||
{
|
||||
padding-inline-start: 20px;
|
||||
}
|
||||
|
||||
*:not(pre) > code
|
||||
{
|
||||
background-color: var(--color_inlinecodebg);
|
||||
border-radius: 4px;
|
||||
line-height: 1.5;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
pre
|
||||
{
|
||||
padding: 8px;
|
||||
border: 1px solid var(--color_codeborder);
|
||||
background-color: var(--color_codebg);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
code,
|
||||
pre,
|
||||
.highlight *
|
||||
{
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/*
|
||||
Thank you richleland for pre-building this Monokai style.
|
||||
https://github.com/richleland/pygments-css
|
||||
*/
|
||||
:root
|
||||
{
|
||||
--color_monokai_bg: #272822;
|
||||
--color_monokai_purple: #ae81ff;
|
||||
--color_monokai_green: #a6e22e;
|
||||
--color_monokai_pink: #f92672;
|
||||
--color_monokai_white: #f8f8f2;
|
||||
--color_monokai_orange: #fd971f;
|
||||
--color_monokai_yellow: #e6db74;
|
||||
--color_monokai_blue: #66d9ef;
|
||||
}
|
||||
.highlight .hll { background-color: #49483e }
|
||||
.highlight { background-color: var(--color_monokai_bg); color: var(--color_monokai_white) }
|
||||
.highlight .c { color: #75715e } /* Comment */
|
||||
.highlight .err { color: #960050; background-color: #1e0010 } /* Error */
|
||||
.highlight .k { color: var(--color_monokai_pink) } /* Keyword */
|
||||
.highlight .l { color: var(--color_monokai_purple) } /* Literal */
|
||||
.highlight .n { color: var(--color_monokai_white) } /* Name */
|
||||
.highlight .o { color: var(--color_monokai_pink) } /* Operator */
|
||||
.highlight .p { color: var(--color_monokai_white) } /* Punctuation */
|
||||
.highlight .ch { color: #75715e } /* Comment.Hashbang */
|
||||
.highlight .cm { color: #75715e } /* Comment.Multiline */
|
||||
.highlight .cp { color: #75715e } /* Comment.Preproc */
|
||||
.highlight .cpf { color: #75715e } /* Comment.PreprocFile */
|
||||
.highlight .c1 { color: #75715e } /* Comment.Single */
|
||||
.highlight .cs { color: #75715e } /* Comment.Special */
|
||||
.highlight .gd { color: var(--color_monokai_pink) } /* Generic.Deleted */
|
||||
.highlight .ge { font-style: italic } /* Generic.Emph */
|
||||
.highlight .gi { color: var(--color_monokai_green) } /* Generic.Inserted */
|
||||
.highlight .gs { font-weight: bold } /* Generic.Strong */
|
||||
.highlight .gu { color: #75715e } /* Generic.Subheading */
|
||||
.highlight .kc { color: var(--color_monokai_purple) } /* Keyword.Constant */
|
||||
.highlight .kd { color: var(--color_monokai_blue) } /* Keyword.Declaration */
|
||||
.highlight .kn { color: var(--color_monokai_pink) } /* Keyword.Namespace */
|
||||
.highlight .kp { color: var(--color_monokai_blue) } /* Keyword.Pseudo */
|
||||
.highlight .kr { color: var(--color_monokai_blue) } /* Keyword.Reserved */
|
||||
.highlight .kt { color: var(--color_monokai_blue) } /* Keyword.Type */
|
||||
.highlight .ld { color: var(--color_monokai_yellow) } /* Literal.Date */
|
||||
.highlight .m { color: var(--color_monokai_purple) } /* Literal.Number */
|
||||
.highlight .s { color: var(--color_monokai_yellow) } /* Literal.String */
|
||||
.highlight .na { color: var(--color_monokai_white) } /* Name.Attribute */
|
||||
.highlight .narg {color: var(--color_monokai_orange) } /* Custom Name.Argument */
|
||||
.highlight .nb { color: var(--color_monokai_blue) } /* Name.Builtin */
|
||||
.highlight .nc { color: var(--color_monokai_white) } /* Name.Class */
|
||||
.highlight .no { color: var(--color_monokai_blue) } /* Name.Constant */
|
||||
.highlight .nd { color: var(--color_monokai_green) } /* Name.Decorator */
|
||||
.highlight .ni { color: var(--color_monokai_white) } /* Name.Entity */
|
||||
.highlight .ne { color: var(--color_monokai_blue) } /* Name.Exception */
|
||||
.highlight .nf { color: var(--color_monokai_green) } /* Name.Function */
|
||||
.highlight .nl { color: var(--color_monokai_white) } /* Name.Label */
|
||||
.highlight .nn { color: var(--color_monokai_white) } /* Name.Namespace */
|
||||
.highlight .nx { color: var(--color_monokai_green) } /* Name.Other */
|
||||
.highlight .py { color: var(--color_monokai_white) } /* Name.Property */
|
||||
.highlight .nt { color: var(--color_monokai_pink) } /* Name.Tag */
|
||||
.highlight .nv { color: var(--color_monokai_white) } /* Name.Variable */
|
||||
.highlight .ow { color: var(--color_monokai_pink) } /* Operator.Word */
|
||||
.highlight .w { color: var(--color_monokai_white) } /* Text.Whitespace */
|
||||
.highlight .mb { color: var(--color_monokai_purple) } /* Literal.Number.Bin */
|
||||
.highlight .mf { color: var(--color_monokai_purple) } /* Literal.Number.Float */
|
||||
.highlight .mh { color: var(--color_monokai_purple) } /* Literal.Number.Hex */
|
||||
.highlight .mi { color: var(--color_monokai_purple) } /* Literal.Number.Integer */
|
||||
.highlight .mo { color: var(--color_monokai_purple) } /* Literal.Number.Oct */
|
||||
.highlight .sa { color: var(--color_monokai_white) } /* Literal.String.Affix */
|
||||
.highlight .sb { color: var(--color_monokai_yellow) } /* Literal.String.Backtick */
|
||||
.highlight .sc { color: var(--color_monokai_yellow) } /* Literal.String.Char */
|
||||
.highlight .dl { color: var(--color_monokai_yellow) } /* Literal.String.Delimiter */
|
||||
.highlight .sd { color: var(--color_monokai_yellow) } /* Literal.String.Doc */
|
||||
.highlight .s2 { color: var(--color_monokai_yellow) } /* Literal.String.Double */
|
||||
.highlight .se { color: var(--color_monokai_purple) } /* Literal.String.Escape */
|
||||
.highlight .sh { color: var(--color_monokai_yellow) } /* Literal.String.Heredoc */
|
||||
.highlight .si { color: var(--color_monokai_yellow) } /* Literal.String.Interpol */
|
||||
.highlight .sx { color: var(--color_monokai_yellow) } /* Literal.String.Other */
|
||||
.highlight .sr { color: var(--color_monokai_yellow) } /* Literal.String.Regex */
|
||||
.highlight .s1 { color: var(--color_monokai_yellow) } /* Literal.String.Single */
|
||||
.highlight .ss { color: var(--color_monokai_yellow) } /* Literal.String.Symbol */
|
||||
.highlight .bp { color: var(--color_monokai_white) } /* Name.Builtin.Pseudo */
|
||||
.highlight .fm { color: var(--color_monokai_blue) } /* Name.Function.Magic */
|
||||
.highlight .vc { color: var(--color_monokai_white) } /* Name.Variable.Class */
|
||||
.highlight .vg { color: var(--color_monokai_white) } /* Name.Variable.Global */
|
||||
.highlight .vi { color: var(--color_monokai_white) } /* Name.Variable.Instance */
|
||||
.highlight .vm { color: var(--color_monokai_white) } /* Name.Variable.Magic */
|
||||
.highlight .il { color: var(--color_monokai_purple) } /* Literal.Number.Integer.Long */
|
|
@ -1,18 +1,43 @@
|
|||
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
|
||||
CSS_CONTENT = PHOTOGRAPHY_ROOTDIR.with_child('dark.css').read('r', encoding='utf-8')
|
||||
DOMAIN_WEBROOT = ('file:///' + DOMAIN_ROOTDIR.absolute_path) if '--test' in sys.argv else 'https://voussoir.net'
|
||||
DOMAIN_WEBROOT = DOMAIN_WEBROOT.replace('\\', '/')
|
||||
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('/')
|
||||
|
@ -22,92 +47,129 @@ def webpath(path, anchor=None):
|
|||
return path
|
||||
|
||||
class Photo:
|
||||
def __init__(self, filepath):
|
||||
self.filepath = filepath
|
||||
self.thumbnail = make_thumbnail(filepath)
|
||||
self.article_id = filepath.replace_extension('').basename
|
||||
self.anchor = f'#{self.article_id}'
|
||||
self.published = imagetools.get_exif_datetime(filepath)
|
||||
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'<span class="number_tag">#{index}/{totalcount}</a>'
|
||||
else:
|
||||
number_tag = ''
|
||||
|
||||
def render_web(self, relative_directory=None):
|
||||
return f'''
|
||||
<article id="{self.article_id}" class="photograph">
|
||||
<a href="{webpath(self.filepath)}" target="_blank"><img src="{webpath(self.thumbnail)}" loading="lazy"/></a>
|
||||
<article id="{self.article_id}" class="photograph {self.color_class}">
|
||||
<a href="{self.img_url}" target="_blank"><img src="{self.small_url}" loading="lazy"/></a>
|
||||
{number_tag}
|
||||
</article>
|
||||
'''
|
||||
|
||||
def render_atom(self):
|
||||
href = webpath(PHOTOGRAPHY_ROOTDIR, anchor=self.anchor)
|
||||
imgsrc = webpath(self.thumbnail)
|
||||
return f'''
|
||||
<id>{self.article_id}</id>
|
||||
<title>{self.article_id}</title>
|
||||
<link rel="alternate" type="text/html" href="{href}"/>
|
||||
<link rel="alternate" type="text/html" href="{self.anchor_url}"/>
|
||||
<updated>{self.published.isoformat()}</updated>
|
||||
<content type="html">
|
||||
<![CDATA[
|
||||
<a href="{href}"><img src="{imgsrc}"/></a>
|
||||
<a href="{self.img_url}"><img src="{self.small_url}"/></a>
|
||||
]]>
|
||||
</content>
|
||||
'''
|
||||
|
||||
class Album:
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
self.article_id = path.basename
|
||||
self.link = webpath(path)
|
||||
self.published = imagetools.get_exif_datetime(sorted(path.glob_files('*.jpg'))[0])
|
||||
self.photos = list(spinal.walk(
|
||||
self.path,
|
||||
glob_filenames={'*.jpg'},
|
||||
exclude_filenames={'*_small*'},
|
||||
recurse=False,
|
||||
yield_directories=False,
|
||||
))
|
||||
self.photos.sort(key=lambda file: file.basename)
|
||||
self.photos = [Photo(file) for file in self.photos]
|
||||
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 render_web(self):
|
||||
firsts = self.photos[:5]
|
||||
remaining = self.photos[5:]
|
||||
if remaining:
|
||||
next_after_more = remaining[0]
|
||||
else:
|
||||
next_after_more = None
|
||||
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="{{article_id}}" class="album">
|
||||
<h1><a href="{{album_path}}">{{directory.basename}}</a></h1>
|
||||
{% for photo in firsts %}
|
||||
<h1><a href="{{web_url}}">{{article_id}}</a></h1>
|
||||
<div class="albumphotos">
|
||||
{% for photo in headliners %}
|
||||
{{photo.render_web()}}
|
||||
{% endfor %}
|
||||
|
||||
{% if remaining > 0 %}
|
||||
<p class="morelink"><a href="{{album_path}}{{next_after_more.anchor}}">{{remaining}} more</a></p>
|
||||
{% endif %}
|
||||
<div class="album_tinies">
|
||||
{% for photo in photos %}
|
||||
<a class="tiny_thumbnail {{photo.color_class}}" href="{{photo.anchor_url}}"><img src="{{photo.tiny_url}}" loading="lazy"/></a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
''').render(
|
||||
article_id=self.article_id,
|
||||
directory=self.path,
|
||||
album_path=webpath(self.path),
|
||||
next_after_more=next_after_more,
|
||||
firsts=firsts,
|
||||
remaining=len(remaining),
|
||||
web_url=self.web_url,
|
||||
photos=self.photos,
|
||||
headliners=headliners,
|
||||
)
|
||||
|
||||
def render_atom(self):
|
||||
photos = []
|
||||
for photo in self.photos:
|
||||
href = webpath(photo.filepath)
|
||||
imgsrc = webpath(photo.thumbnail)
|
||||
line = f'<article><a href="{href}"><img src="{imgsrc}"/></a>'.replace('\\', '/')
|
||||
line = f'<article><a href="{photo.anchor_url}"><img src="{photo.small_url}" loading="lazy"/></a>'.replace('\\', '/')
|
||||
photos.append(line)
|
||||
photos = '\n'.join(photos)
|
||||
|
||||
return f'''
|
||||
<id>{self.article_id}</id>
|
||||
<title>{self.article_id}</title>
|
||||
<link rel="alternate" type="text/html" href="{self.link}"/>
|
||||
<link rel="alternate" type="text/html" href="{self.web_url}"/>
|
||||
<updated>{self.published.isoformat()}</updated>
|
||||
<content type="html">
|
||||
<![CDATA[
|
||||
|
@ -126,30 +188,12 @@ def write(path, content):
|
|||
print(path.absolute_path)
|
||||
path.write('w', content, encoding='utf-8')
|
||||
|
||||
def write_directory_index(directory):
|
||||
rss_link = webpath(ATOM_FILE) if directory == PHOTOGRAPHY_ROOTDIR else None
|
||||
back_link = webpath(PHOTOGRAPHY_ROOTDIR) if directory != PHOTOGRAPHY_ROOTDIR else None
|
||||
sort_reverse = directory == PHOTOGRAPHY_ROOTDIR
|
||||
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
|
||||
|
||||
items = list(spinal.walk(
|
||||
directory,
|
||||
glob_filenames={'*.jpg'},
|
||||
exclude_filenames={'*_small*'},
|
||||
recurse=False,
|
||||
yield_directories=False,
|
||||
)) + list(directory.listdir_directories())
|
||||
|
||||
items2 = []
|
||||
for item in items:
|
||||
if item.is_file:
|
||||
items2.append(Photo(item))
|
||||
else:
|
||||
items2.append(Album(item))
|
||||
|
||||
items = items2
|
||||
items.sort(key=lambda item: item.published, reverse=sort_reverse)
|
||||
|
||||
page = jinja2.Template('''
|
||||
html = jinja2.Template('''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
@ -158,10 +202,229 @@ def write_directory_index(directory):
|
|||
{% if rss_link %}
|
||||
<link rel="alternate" type="application/atom+xml" href="{{rss_link}}"/>
|
||||
{% endif %}
|
||||
<title>{{directory.basename}}</title>
|
||||
<title>{{doctitle}}</title>
|
||||
|
||||
<style>
|
||||
{{css_content}}
|
||||
:root
|
||||
{
|
||||
--color_bodybg: #272822;
|
||||
--color_htmlbg: #1b1c18;
|
||||
--color_link: #ae81ff;
|
||||
--color_maintext: #ddd;
|
||||
|
||||
--img_borderradius: 16px;
|
||||
--img_borderradius_tiny: 4px;
|
||||
--img_sepia: 0%;
|
||||
--img_huerotate: 0deg;
|
||||
--img_saturate: 100%;
|
||||
--img_blur: 0px;
|
||||
--img_mixblendmode: normal;
|
||||
}
|
||||
|
||||
*, *:before, *:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hidden
|
||||
{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
html
|
||||
{
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
|
||||
background-color: var(--color_htmlbg);
|
||||
color: var(--color_maintext);
|
||||
|
||||
font-family: Verdana, sans-serif;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
width: 100em;
|
||||
max-width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding: 8px;
|
||||
padding-bottom:8vh;
|
||||
|
||||
display: grid;
|
||||
grid-auto-flow: row;
|
||||
grid-row-gap: 12vh;
|
||||
}
|
||||
body.noscrollbar::-webkit-scrollbar
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
body.noscrollbar
|
||||
{
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
a
|
||||
{
|
||||
color: var(--color_link);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h1
|
||||
{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
article *
|
||||
{
|
||||
max-width: 100%;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
header
|
||||
{
|
||||
width: 100%;
|
||||
max-width: 120em;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: end;
|
||||
}
|
||||
header > *
|
||||
{
|
||||
display: inline-block;
|
||||
padding: 16px;
|
||||
background-color: var(--color_bodybg);
|
||||
}
|
||||
|
||||
.album,
|
||||
.photograph,
|
||||
.album_tinies
|
||||
{
|
||||
position: relative;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.album
|
||||
{
|
||||
width: 100%;
|
||||
}
|
||||
.album .albumphotos
|
||||
{
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-auto-flow: row;
|
||||
grid-row-gap: 12vh;
|
||||
}
|
||||
.photograph
|
||||
{
|
||||
width: fit-content;
|
||||
}
|
||||
.photograph img
|
||||
{
|
||||
display: block;
|
||||
max-height: 92vh;
|
||||
border-radius: var(--img_borderradius);
|
||||
border: 1.25vh solid var(--color_bodybg);
|
||||
filter: hue-rotate(var(--img_huerotate)) saturate(var(--img_saturate)) blur(var(--img_blur));
|
||||
mix-blend-mode: var(--img_mixblendmode);
|
||||
}
|
||||
.photograph.monochrome img,
|
||||
.tiny_thumbnail.monochrome img
|
||||
{
|
||||
filter: sepia(var(--img_sepia)) hue-rotate(var(--img_huerotate)) saturate(var(--img_saturate)) blur(var(--img_blur));
|
||||
}
|
||||
.photograph .number_tag
|
||||
{
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
text-align: center;
|
||||
background-color: white;
|
||||
padding: 1px;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
font-family: sans-serif;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
opacity: 50%;
|
||||
}
|
||||
.album .album_tinies
|
||||
{
|
||||
max-width: 80em;
|
||||
text-align: justify;
|
||||
}
|
||||
.tiny_thumbnail
|
||||
{
|
||||
vertical-align: middle;
|
||||
display:inline-block;
|
||||
margin: 8px;
|
||||
}
|
||||
.tiny_thumbnail img
|
||||
{
|
||||
aspect-ratio: auto;
|
||||
border-radius: var(--img_borderradius_tiny);
|
||||
outline: 4px solid var(--color_bodybg);
|
||||
filter: hue-rotate(var(--img_huerotate)) saturate(var(--img_saturate)) blur(var(--img_blur));
|
||||
mix-blend-mode: var(--img_mixblendmode);
|
||||
}
|
||||
|
||||
@media not print
|
||||
{
|
||||
.photograph img
|
||||
{
|
||||
box-shadow: #000 0px 0px 40px -10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px)
|
||||
{
|
||||
.tiny_thumbnail img
|
||||
{
|
||||
height: 128px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px)
|
||||
{
|
||||
.tiny_thumbnail img
|
||||
{
|
||||
height: 64px;
|
||||
}
|
||||
.photograph
|
||||
{
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media not all and (pointer: fine)
|
||||
{
|
||||
#keyboardhint,
|
||||
#scrollbartoggle
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {font-size: 2.00em;} h1 * {font-size: inherit;}
|
||||
h2 {font-size: 1.75em;} h2 * {font-size: inherit;}
|
||||
h3 {font-size: 1.50em;} h3 * {font-size: inherit;}
|
||||
h4 {font-size: 1.25em;} h4 * {font-size: inherit;}
|
||||
h5 {font-size: 1.00em;} h5 * {font-size: inherit;}
|
||||
|
||||
#a_new_perspective
|
||||
{
|
||||
background-color: var(--color_bodybg);
|
||||
position: fixed;
|
||||
left: 8px;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
@ -178,9 +441,47 @@ def write_directory_index(directory):
|
|||
{%- endif -%}
|
||||
</header>
|
||||
|
||||
{% if not is_root %}
|
||||
<h1>{{doctitle}}</h1>
|
||||
{% endif %}
|
||||
|
||||
{% for item in items %}
|
||||
{{item.render_web()}}
|
||||
{{item.render_web(index=loop.index, totalcount=none if is_root else (items|length))}}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
<footer>
|
||||
<p>Ethan Dalool</p>
|
||||
<p>Contact me: photography@voussoir.net</p>
|
||||
</footer>
|
||||
|
||||
<p><button id="new_perspective_button" onclick="return new_perspective_button_onclick(event);">👁️ Try a different perspective</button></p>
|
||||
|
||||
<form id="a_new_perspective" class="hidden">
|
||||
<div><label>Background color: <input type="color" value="#1b1c18" oninput="return backgroundcolor_onchange(event);"/></label></div>
|
||||
<div><label>Border radius: <input type="range" min="0" value="16" max="500" oninput="return border_radius_onchange(event);"/></label> <span id="border_radius_value">16px</span></div>
|
||||
<div><label>Saturation: <input type="range" min="0" value="100" max="500" oninput="return saturate_onchange(event);"/></label> <span id="saturate_value">100%</span></div>
|
||||
<div><label>Hue rotate: <input type="range" min="0" value="0" max="360" oninput="return hue_rotate_onchange(event);"/></label> <span id="hue_rotate_value">0deg</span></div>
|
||||
<div><label>Blur: <input type="range" min="0" value="0" max="50" oninput="return blur_onchange(event);"/></label> <span id="blur_value">0%</span></div>
|
||||
<div><label>Mix blend mode: <select onchange="return mixblendmode_onchange(event);">
|
||||
<option selected>normal</option>
|
||||
<option>multiply</option>
|
||||
<option>screen</option>
|
||||
<option>overlay</option>
|
||||
<option>darken</option>
|
||||
<option>lighten</option>
|
||||
<option>color-dodge</option>
|
||||
<option>color-burn</option>
|
||||
<option>hard-light</option>
|
||||
<option>soft-light</option>
|
||||
<option>difference</option>
|
||||
<option>exclusion</option>
|
||||
<option>hue</option>
|
||||
<option>saturation</option>
|
||||
<option>color</option>
|
||||
<option>luminosity</option>
|
||||
</select></label></div>
|
||||
</form>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
@ -212,70 +513,93 @@ def write_directory_index(directory):
|
|||
}
|
||||
}
|
||||
|
||||
function get_center_img()
|
||||
const SCROLL_STOPS = Array.from(document.querySelectorAll("article.photograph img, .album_tinies"));
|
||||
function get_center_stop()
|
||||
{
|
||||
let center_x = window.innerWidth / 2;
|
||||
let center_y = window.innerHeight / 2;
|
||||
while (true)
|
||||
let center_y = window.pageYOffset + (window.innerHeight / 2);
|
||||
let final;
|
||||
for (const stop of SCROLL_STOPS)
|
||||
{
|
||||
const element = document.elementFromPoint(center_x, center_y);
|
||||
if (element.tagName === "IMG")
|
||||
const stop_top = stop.getBoundingClientRect().top + window.pageYOffset;
|
||||
const stop_bottom = stop_top + stop.offsetHeight;
|
||||
console.log("-----");
|
||||
console.log(stop);
|
||||
console.log(`${center_y} versus ${stop_top}--${stop_bottom}`);
|
||||
console.log("-----");
|
||||
if (center_y > stop_top)
|
||||
{
|
||||
return element;
|
||||
final = stop;
|
||||
}
|
||||
center_y -= 20;
|
||||
if (center_y <= 0)
|
||||
else
|
||||
{
|
||||
return null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (final)
|
||||
{
|
||||
return final;
|
||||
}
|
||||
}
|
||||
function next_img(img)
|
||||
{
|
||||
const images = Array.from(document.images);
|
||||
const this_index = images.indexOf(img);
|
||||
if (this_index === images.length-1)
|
||||
const this_index = SCROLL_STOPS.indexOf(img);
|
||||
if (this_index === SCROLL_STOPS.length-1)
|
||||
{
|
||||
return img;
|
||||
}
|
||||
return images[this_index + 1];
|
||||
return SCROLL_STOPS[this_index + 1];
|
||||
}
|
||||
function previous_img(img)
|
||||
{
|
||||
const images = Array.from(document.images);
|
||||
const this_index = images.indexOf(img);
|
||||
const this_index = SCROLL_STOPS.indexOf(img);
|
||||
if (this_index === 0)
|
||||
{
|
||||
return img;
|
||||
}
|
||||
return images[this_index - 1];
|
||||
return SCROLL_STOPS[this_index - 1];
|
||||
}
|
||||
function scroll_step()
|
||||
{
|
||||
const distance = desired_scroll_position - document.body.scrollTop;
|
||||
const jump = (distance * 0.25) + (document.body.scrollTop < desired_scroll_position ? 1 : -1);
|
||||
document.body.scrollTop = document.body.scrollTop + jump;
|
||||
console.log(`${document.body.scrollTop} ${desired_scroll_position}`);
|
||||
const new_distance = desired_scroll_position - document.body.scrollTop;
|
||||
const distance = desired_scroll_position - document.documentElement.scrollTop;
|
||||
const jump = (distance * 0.25) + (document.documentElement.scrollTop < desired_scroll_position ? 1 : -1);
|
||||
document.documentElement.scrollTop = document.documentElement.scrollTop + jump;
|
||||
//console.log(`${document.documentElement.scrollTop} ${desired_scroll_position}`);
|
||||
const new_distance = desired_scroll_position - document.documentElement.scrollTop;
|
||||
if (Math.abs(new_distance / distance) < 0.97)
|
||||
{
|
||||
window.requestAnimationFrame(scroll_step);
|
||||
}
|
||||
}
|
||||
function scroll_to_img(img)
|
||||
function scroll_to_stop(stop)
|
||||
{
|
||||
const img_centerline = img.getBoundingClientRect().top + img.ownerDocument.defaultView.pageYOffset + (img.offsetHeight / 2);
|
||||
// document.body.scrollTop = img_centerline - (window.innerHeight / 2);
|
||||
desired_scroll_position = Math.round(img_centerline - (window.innerHeight / 2));
|
||||
if (stop.offsetHeight > window.innerHeight)
|
||||
{
|
||||
desired_scroll_position = stop.getBoundingClientRect().top + window.pageYOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
const img_centerline = stop.getBoundingClientRect().top + window.pageYOffset + (stop.offsetHeight / 2);
|
||||
// document.documentElement.scrollTop = img_centerline - (window.innerHeight / 2);
|
||||
desired_scroll_position = Math.round(img_centerline - (window.innerHeight / 2));
|
||||
}
|
||||
scroll_step();
|
||||
}
|
||||
function scroll_to_next_img()
|
||||
{
|
||||
scroll_to_img(next_img(get_center_img()));
|
||||
const current_stop = get_center_stop();
|
||||
if (current_stop)
|
||||
{
|
||||
scroll_to_stop(next_img(current_stop));
|
||||
}
|
||||
}
|
||||
function scroll_to_previous_img()
|
||||
{
|
||||
scroll_to_img(previous_img(get_center_img()));
|
||||
const current_stop = get_center_stop();
|
||||
if (current_stop)
|
||||
{
|
||||
scroll_to_stop(previous_img(current_stop));
|
||||
}
|
||||
}
|
||||
function arrowkey_listener(event)
|
||||
{
|
||||
|
@ -304,6 +628,78 @@ def write_directory_index(directory):
|
|||
clearTimeout(hide_cursor_timeout);
|
||||
hide_cursor_timeout = setTimeout(hide_cursor, 3000);
|
||||
}
|
||||
|
||||
function new_perspective_button_onclick(event)
|
||||
{
|
||||
const p = event.target.parentElement;
|
||||
document.getElementById("a_new_perspective").classList.remove("hidden");
|
||||
p.parentElement.removeChild(p);
|
||||
}
|
||||
|
||||
function backgroundcolor_onchange(event)
|
||||
{
|
||||
document.documentElement.style.setProperty("--color_htmlbg", event.target.value);
|
||||
}
|
||||
|
||||
function border_radius_onchange(event)
|
||||
{
|
||||
if (event.target.value == "500")
|
||||
{
|
||||
const value = "100%";
|
||||
document.documentElement.style.setProperty("--img_borderradius", value);
|
||||
document.documentElement.style.setProperty("--img_borderradius_tiny", value);
|
||||
document.getElementById("border_radius_value").textContent = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
const value = event.target.value + "px";
|
||||
document.documentElement.style.setProperty("--img_borderradius", value);
|
||||
document.documentElement.style.setProperty("--img_borderradius_tiny", (event.target.value / 4) + "px");
|
||||
document.getElementById("border_radius_value").textContent = value;
|
||||
}
|
||||
}
|
||||
|
||||
function saturate_onchange(event)
|
||||
{
|
||||
const value = event.target.value + "%";
|
||||
document.documentElement.style.setProperty("--img_saturate", value);
|
||||
if (event.target.value == "0")
|
||||
{
|
||||
document.getElementById("saturate_value").textContent = "Artistic";
|
||||
}
|
||||
else
|
||||
{
|
||||
document.getElementById("saturate_value").textContent = value;
|
||||
}
|
||||
}
|
||||
|
||||
function hue_rotate_onchange(event)
|
||||
{
|
||||
const value = event.target.value + "deg";
|
||||
if (event.target.value === "0")
|
||||
{
|
||||
document.documentElement.style.setProperty("--img_sepia", "0");
|
||||
}
|
||||
else
|
||||
{
|
||||
document.documentElement.style.setProperty("--img_sepia", "100%");
|
||||
}
|
||||
document.documentElement.style.setProperty("--img_huerotate", value);
|
||||
document.getElementById("hue_rotate_value").textContent = value;
|
||||
}
|
||||
|
||||
function blur_onchange(event)
|
||||
{
|
||||
const value = event.target.value + "px";
|
||||
document.documentElement.style.setProperty("--img_blur", value);
|
||||
document.getElementById("blur_value").textContent = value;
|
||||
}
|
||||
|
||||
function mixblendmode_onchange(event)
|
||||
{
|
||||
document.documentElement.style.setProperty("--img_mixblendmode", event.target.value);
|
||||
}
|
||||
|
||||
function on_pageload()
|
||||
{
|
||||
document.documentElement.addEventListener("keydown", arrowkey_listener);
|
||||
|
@ -315,23 +711,20 @@ def write_directory_index(directory):
|
|||
</script>
|
||||
</html>
|
||||
''').render(
|
||||
css_content=CSS_CONTENT,
|
||||
directory=directory,
|
||||
is_root=is_root,
|
||||
doctitle=doctitle,
|
||||
rss_link=rss_link,
|
||||
back_link=back_link,
|
||||
items=items,
|
||||
)
|
||||
write(directory.with_child('index.html'), page)
|
||||
|
||||
if rss_link:
|
||||
write_atom(items)
|
||||
return html
|
||||
|
||||
def write_atom(items):
|
||||
atom = jinja2.Template('''
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>voussoir.net/photography</title>
|
||||
<link href="webpath(PHOTOGRAPHY_ROOTDIR)}"/>
|
||||
<link href="https://voussoir.net/photography"/>
|
||||
<id>voussoir.net/photography</id>
|
||||
|
||||
{% for item in items %}
|
||||
|
@ -343,21 +736,41 @@ def write_atom(items):
|
|||
'''.strip()).render(items=items)
|
||||
write(ATOM_FILE, atom)
|
||||
|
||||
def make_thumbnail(photo):
|
||||
small_name = photo.replace_extension('').basename + '_small'
|
||||
small_name = photo.parent.with_child(small_name).add_extension(photo.extension)
|
||||
if small_name.is_file:
|
||||
return small_name
|
||||
image = PIL.Image.open(photo.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, 1440, 1440)
|
||||
image = image.resize((width, height), PIL.Image.LANCZOS)
|
||||
image.save(small_name.absolute_path, quality=75, exif=exif, icc_profile=icc)
|
||||
print(small_name)
|
||||
return small_name
|
||||
# write_directory_index(PHOTOGRAPHY_ROOTDIR)
|
||||
# for directory in PHOTOGRAPHY_ROOTDIR.walk_directories():
|
||||
# write_directory_index(directory)
|
||||
|
||||
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:]))
|
||||
|
|
Loading…
Reference in a new issue