#!/usr/bin/python3
"""
===========================================================================
thumbspage.py, version 1.5, Aug-12-2018
Synopsis: Make an HTML thumbnail-links page plus image-viewer pages
for the images in a source folder.
Requires: Any Python 3.X, plus the Pillow (PIL) image library
available at https://pypi.python.org/pypi/Pillow.
Launch: Run thumbspage.py, input run parameters in console.
See also option setting variables at start of code.
Examples: See learning-python.com/trnpix, and /examples here.
License: Provided freely but with no warranties of any kind.
Author: Copyright M. Lutz (learning-python.com), 2016-2018.
===========================================================================
OVERVIEW
Given a folder of image files, this script generates a basic HTML
index page with thumbnail links for each image file in the folder
that open either a generated viewer page or the image directly.
The script skips non-image files, uses optional header and footer
HTML inserts, makes an automatic bullet list for any subfolders
in the images folder (not named with a leading "_"), and creates
the output index page in the images folder itself, along with a
"thumbs" subfolder for thumbnail images and viewer pages' HTML.
As of 1.3, non-ASCII Unicode filenames and content are supported.
As of 1.4, all output pages are more mobile-friendly. As of 1.5,
formatted image-viewer pages with next/previous links can also be
generated; when omitted, index links open images per browsers.
To view results, open the output "index.html" index page created in
your images folder. To publish the page, upload the entire images
folder, including its generated "thumbs" subfolder and "index.html"
file, to the folder representing your page on your site's web-server
host; zip first for convenience. The names of the generated index
page and thumbs folder can both be changed in settings below.
For more custom behavior, add custom HTML code to the top and bottom
of the output page by placing it in files in the images folder named
"HEADER.html" and "FOOTER.html" respectively (both are optional).
If these files are not present, generic HTML and text is generated
in the output page around the thumbs table. For details on how to
code these files, see the examples in examples/trnpix. In brief:
- HEADER.html should be a full HTML preamble (with doctype, mobile
viewport, content type/encoding, and any styles) and begin
- FOOTER.html should add any post-table content and close the
===========================================================================
USAGE NOTE
Because image scaling is weak in CSS (and JavaScript is both overkill
and nonstarter for this program), the image-viewer pages added in
version 1.5 should be considered an optional feature.
These pages can be disabled via the last console reply (see the next
section) if the former browser-native view is preferred. If these
pages are used, your desktop users may wish to resize windows for
optimal view when needed, and click "Raw" for browser-native view
when desired. Mobile users need not resize, but can tap "Raw" to
go native. Despite viewer-pages' scaling limits, their "Prev"/"Next"
navigation links are still a compelling feature in most use cases.
===========================================================================
USAGE EXAMPLE
/.../thumbspage/_private/test15$ python3 ../../thumbspage.py
Clean thumbs folder [y or n] (enter=n)? y
Thumbs per row [int] (enter=4)?
Thumb max size [x, y] (enter=(100, 100))?
Images folder path [. or dir] (enter=.)? trnpix
Use image-viewer pages [y or n] (enter=n)? y
Running
Cleaning: trnpix/thumbs/1996-first-pybook.png
Cleaning: trnpix/thumbs/1996-first-pybook.png.html
...
Skipping: .DS_Store
Making thumbnail: trnpix/thumbs/1996-first-pybook.png
Making thumbnail: trnpix/thumbs/1998-puertorico-1.jpg
...
Skipping: _cut
Skipping: _HOW.txt
...
Generating thumbnails index page
Generating view page for: 1996-first-pybook.png
Generating view page for: 1998-puertorico-1.jpg
...
Finished: see results in images folder.
===========================================================================
GUI MODE
You can also view the thumbs and images in GUI mode (i.e., without a
web browser) by using the PP4E book example included and used here
on Windows or Unix (Mac OS and Linux):
c:\...\thumbspage> py -3.3 viewer_thumbs.py examples\test\images
/.../thumbspage$ python3 viewer_thumbs.py examples/test/images
This is a very basic GUI view, however; the book's later PyPhoto
example adds scroll bars to both the thumbs index and open images.
Update: a much-upgraded PyPhoto is now available standalone, at
http://learning-python.com/pygadgets.html (it's a bundled gadget).
===========================================================================
VERSION HISTORY
1.5, Aug-12-18: formatted image-viewer pages.
Generate a styled viewer page for each image in the folder,
instead of relying on each browser's native image display.
These new viewer pages are an addition to the former index
page. They are opened on index-page thumbnail clicks, and
have filename, the image scaled per CSS, view-native and
goto-index links, and previous/next-image links that cycle
through all images without having to return to the index page.
Viewer pages are generated in the thumbs folder, along with
the prior version's thumbnail-index image files. They can
also be suppressed via console input prompts; when omitted,
images open in browsers directly and natively, as before.
An example client - http://learning-python.com/trnpix/
Caveat: because image scaling is weak in CSS (and JavaScript
is overkill here), image-viewer pages should be considered
an optional feature. See "USAGE NOTE" above for deployment
suggestions, and code notes below for more details.
1.5 also:
- Formalizes index/navigation ordering (it's now case sensitive
by default everywhere, but can be changed in settings below).
- Formalizes URL-escapes Unicode encoding (it's now always
UTF-8 regardless of the encoding used for whole HTML pages,
because this seems to be required by standards and browsers).
- Sets body font to Arial (sans serif) for default-header index
pages (which looks nicer and matches new viewer pages' font,
but is not inlined so it can differ in a custom HEADER.html).
- Works around a desktop Chrome
bug (see "CAVEATS" #6
ahead), by restyling the thumbs table with table borders
instead of
s. As a consequence, the thumbs table now
always stretches to 100% window width, to extend the border
lines (this was formerly a configuration, off by default).
- Sets thumbs-table background color to light grey as part of
the
restyling, and allows it to be configured in settings
below. Viewer page color can be similarly tailored below.
- Refactors its code to functions (it's now large enough
that top-level code is difficult to navigate), and cleans
up its page output (HTML/CSS is tough to read as it is).
- Still uses all-inline styles for index pages, not '
def generateIndexPage(imageDir):
"""
----------------------------------------------------------------
Build and output the HTML for the thumbnails-index page in the
images folder, referencing already-built thumbnail images.
This uses all-inline CSS styling, because custom HEADER.html
files are not expected to code or link to anything made here.
This also uses individual prints, because most content varies.
----------------------------------------------------------------
"""
print('Generating thumbnails index page')
# collect href lists
imglinks = formatImageLinks(imageDir, styleImgThumbs)
sublinks = formatSubfolderLinks(imageDir)
# don't assume Unicode default (in locale module)
save_stdout = sys.stdout
sys.stdout = open(indexPath, 'w', encoding=outputEncoding)
# header section
if os.path.exists(headerPath):
# assume HTML-safe (pre-escaped), explicit Unicode
insert = open(headerPath, 'r', encoding=insertEncoding)
print(insert.read())
print(''.upper())
else:
foldername = os.path.basename(imageDir)
escfoldername = html_escape(foldername)
print(doctype)
print('')
print(contype)
if useViewPort:
print(viewmeta)
print(indexfont)
print('Index of %s'
'\n\n\n'
'Index of image folder "%s"
' % ((escfoldername,) * 2))
print(''.upper())
# subfolders bullet list (skip other content)
if sublinks and listSubfolders:
print('\n'.upper())
print('Subfolders here:
')
for link in sublinks:
linkstyle = 'style="margin-bottom: 6px;"' # add space for mobile [1.4]
print('- %s' % (linkstyle, link)) # add space below list [1.4]
print('
')
# thumb links table
print('\n'.upper())
print('') # drop
print('') # table autoscroll on small screens [1.4]
# whole-window scroll breaks mobile widgets
# [1.5] styled top/bottom borders, not
print('
' % styleTableThumbs)
# thumb links cells
while imglinks:
row, imglinks = imglinks[:thumbsPerRow], imglinks[thumbsPerRow:]
print('')
for (escname, link) in row:
colstyle = 'style="'
colstyle += 'padding: 3px; ' # avoid running together [1.4]
colstyle += 'text-align: center;' # center img in its cell [1.4]
if uniformColumns:
colstyle += ' width: %d%%;' % (100 / thumbsPerRow)
colstyle += '"'
labstyle = 'style="white-space: nowrap; margin-top: 0px;"'
print('\n\t%s\n\t %s | ' % (colstyle, link, labstyle, escname))
print('
')
#print('|
') # drop in [1.4]: use css as needed
print('
') # drop
# footer section
if os.path.exists(footerPath):
# assume HTML-safe (pre-escaped), explicit Unicode
print('\n'.upper())
insert = open(footerPath, 'r', encoding=insertEncoding)
print(insert.read())
else:
print('\n'.upper())
print('This page was generated by '
'thumbspage.py'
'
')
print('')
sys.stdout.close() # this used to be script exit
sys.stdout = save_stdout # there's now more to the show...
#==========================================================================
# GENERATE image-viewer/navigation web pages in thumbs subfolder [1.5]
#==========================================================================
#
# Template: HTML/CSS, uppercase for % dict-key replacement targets, %% = %
#
viewerTemplate = """
%(IMAGENAME)s
"""
def generateViewerPages(imageDir):
"""
-----------------------------------------------------------------------
Build and output the HTML for one view/navigate page for each image
and thumbnail, opened on thumbnail clicks. Navigation matches the
filesystem-neutral and case-specific image order on index page.
Not run if not useViewerPages, in which case there may be some pages
from prior uncleaned runs, but we aren't linking to them, or making
any now. If run, always makes new viewer pages (unlike thumbs).
This was an afterthought, might be improved with JavaScript for scaling,
and probably should be customizable in general, but its Prev/Next links
already beat browser-native displays. Assumes that thumbnail filenames
are the same as fullsize-image filenames in .., and uses a template
string (not many prints) because most page content is fixed.
The template could be read from a file instead, but modifying a string
here is easy. The CSS could also be generated in a separate file, but
it's easy to mod this code. The CSS need not be inlined here, because
no parts can be custom files. Caveat: this script may need PyPhoto's
pickle-file scheme if many-photo use cases yield too many thumb files.
-----------------------------------------------------------------------
About image scaling: because scaling as implemented here is less than
ideal, viewer pages are optional - see "USAGE NOTE" above for tips.
CSS is poor at this: it cannot emulate better browser-native display.
As is, users may need to resize their desktop windows for optimal
viewing, because images may exceed page containers on window resizes.
This probably requires JavaScript + onresize event callbacks to do
better: CSS has no way to adjust as window size and aspect changes.
Dev notes on the assorted scaling attempts sketched below:
- 'auto' seems busted for width; fractional % usage may vary
- [max-height: 100%%; max-width: 100%%;] doesn't constrain high
- [overflow-x/y: auto;] doesn't limit image size, on div or img
- [object-fit: contain;] attempt didn't work on chrome (why?)
- the math scales side1 to N%, and side2 to N% of its ratio to side1
Failed attempt: portrait not scaled down, landscape no diff
if imgwide >= imghigh:
IMAGEWIDE, IMAGEHIGH = '100%', 'auto'
else:
IMAGEHIGH, IMAGEWIDE = '100%', 'auto'
Failed attempt: portrait too tall, landscape no diff
ratio = min(imgwide / imghigh, imghigh / imgwide)
if imgwide >= imghigh:
IMAGEWIDE, IMAGEHIGH = '100%', '%f%%' % (100 * (imghigh / imgwide))
else:
IMAGEHIGH, IMAGEWIDE = '100%', '%f%%' % (100 * (imgwide / imghigh))
-----------------------------------------------------------------------
"""
allthumbs = orderedListing(os.path.join(imageDir, THUMBS))
allthumbs = [thumb for thumb in allthumbs if isImageFileName(thumb)]
thumb1, thumbN = 0, len(allthumbs) - 1
for ix in range(len(allthumbs)):
thumbname = allthumbs[ix]
imgwide, imghigh = imageWideHigh(imageDir, thumbname)
# these work well on mobile, and on desktop if window sized
if imgwide > imghigh:
IMAGEWIDE, IMAGEHIGH = '100%', 'auto' # landscape
else:
IMAGEHIGH, IMAGEWIDE = '80%', '%f%%' % (80 * (imgwide / imghigh))
vals = dict(
ENCODING = outputEncoding,
BGCOLOR = viewerBgColor,
# relative to thumbs/, always unix "/" on web
IMAGENAME = html_escape(thumbname),
IMAGEPATH = url_escape('../' + thumbname),
# nav links: pages in '.', wrap around at end/start
PREVPAGE = url_escape('%s.html' % allthumbs[(ix-1) if ix > thumb1 else -1]),
NEXTPAGE = url_escape('%s.html' % allthumbs[(ix+1) if ix < thumbN else 0]),
# scale larger dimension to viewport (see calcs and notes above)
IMAGEWIDE = IMAGEWIDE,
IMAGEHIGH = IMAGEHIGH
)
print('Generating view page for: %s' % thumbname)
viewerpath = os.path.join(imageDir, THUMBS, thumbname + '.html')
viewerfile = open(viewerpath, mode='w', encoding=outputEncoding)
viewerfile.write(viewerTemplate % vals)
viewerfile.close()
#==========================================================================
# MAIN LOGIC: kick off the functions above (no longer top-level code [1.5])
#==========================================================================
if __name__ == '__main__':
print('Running')
makeThumbnails(imageDir)
generateIndexPage(imageDir)
if useViewerPages:
generateViewerPages(imageDir)
print('Finished: see results in images folder.')
# and view index.html in images folder, zip/upload images folder to site