#!/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:

') # 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('' % (colstyle, link, labstyle, escname)) print('') #print('') # drop in [1.4]: use css as needed print('
\n\t%s\n\t

%s

') # 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

%(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