File: thumbspage/thumbspage.py

#!/usr/bin/python3
"""
===========================================================================
thumbspage.py, version 1.4, Mar-4-2018
Synopsis: Make an HTML thumbnail links page for an images 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 index 
page with thumbnail links for each image file in the folder.  It 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 page in the images 
folder itself.  As of 1.3, non-ASCII Unicode filenames and content are 
supported.  As of 1.4, its output page is more mobile-friendly.

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.

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: PyPhoto is now available upgraded and standalone, at
  learning-python.com/pygadgets.html.

===========================================================================

Version history:

1.4, Mar-4-18: restyle for mobile and improved table display. 
     This script's output page now better supports browsers on 
     smaller screens, and looks nicer in general.  Its new CSS:

     - Autoscrolls the thumbs table to appease mobile browsers;
     - Adds padding to thumb cells to reduce run-together; 
     - Center-aligns thumbs images for a more even look; this 
       helps overall, but especially for narrow/portrait images; 
     - Uses nowrap paragraphs for image labels; the former <br>+wrap
       scheme looked jumbled, and made Chrome (only!) arrange thumb 
       table columns unevenly in small windows (left was narrower).

     This version also adds a mobile-friendly viewport <meta> tag 
     to default headers if "useViewPort."  This may impact other 
     page components; use a custom HEADER.html for more options.  

     Tip: because filenames used on labels are now not wrapped, 
     their width largely determines column spacing in the index
     page; use shorter filenames for less space between columns.

1.3, Aug-8-16: support non-ASCII Unicode filenames and content:

     a) HTML-escape all added text - image, folder, subfolder names

     b) URL-escape all added links - to thumbs, images, subfolders

     c) Output the index file in UTF8 Unicode encoding by default,
        with a content-type <meta> tag; ASCII content is unchanged,
        as it is a subset of UTF8; other encodings may be used for
        the output file via setting outputEncoding in the code 

     d) Load any header and footer inserts per UTF8 Unicode encoding
        by default, as it is general and supports ASCII directly;
        other encodings, including the platform's default, may be
        used for inserts via setting insertEncoding in the code

     e) Assume any inserts are both HTML-safe and UTF8-compatible

     See examples\test\escapes\images for a Unicode test case.
     
1.2, Aug-1-16: add table styling options, per settings in code:

     a) Uniform-width columns, not per content (_on_ by default)
     b) Stretch thumbs table to fill whole window (_off_ by default)
     c) Scrollbar if window too small? (skipped in 1.2, added in 1.4)

1.1, Jul-27-16: add auto subfolder-links list, per setting in code.

1.0, Jul-24-16: initial release.

===========================================================================

Notes:

1) (all) SCOPE: Apart from header/footer inserts, this script makes
fairly simplistic image and subfolder links only; edit either
the output HTML page or the code here that creates it as desired.

2) (1.1) SUBFOLDERS: Makes a bullet list for any subfolders in the
images folder (else they cannot be navigated to), but skips all
other non-image content; put any doctype, readme, font, CSS code,
etc., in header or footer.  Run on each image tree subfolder manually;
automating this and other content seems too clever and complex.

3) (1.2) STYLING: Stretching the thumbs table to fill the whole window
is off by default, as it can appear too spread out for short names in
large windows.  Enable this via the code setting below if desired.

4) (1.2) STYLING: A table overflow scrollbar was skipped, as it
appears only at table bottom, which can be very far away on large
pages.  Instead, use the browser's automatic general window scroll.
   ==> changed in 1.4: now uses CSS autoscroll for thumbs table,
       which is more mobile-friendly, and shows scroll iff needed.

5) (1.3) UNICODE: The script now fully supports images with non-ASCII
names, but getting them onto your web server may take special steps.
On my Linux server, an "unzip" of the WinZip zip package failed on the
Unicode test images, but GoDaddy's browser-based file upload worked.
See examples/test/escapes/readme.txt for more on uploading files.
See also learning-python.com/ziptools for a Python zip alternative.

6) (1.3) UNICODE: the insertEncoding setting now defaults to UTF-8,
as this is a general scheme which also works for ASCII files.  To use
your platform's default encoding instead, set this variable to None,
or save your header/footer inserts in UTF-8 format.

7) (1.3) UNICODE: If (but only if) file or folder names are non-ASCII,
custom HEADER.html files must include a <meta> content-type tag with
a charset matching the outputEncoding (which is UTF-8 by default).
Default headers include this tag automatically (see the code).

8) (all) RELATED: See also pixindex, a related tool that adds images
to a zip file and FTPs them to a sever: learning-python.com/pixindex.

===========================================================================

Caveats and TBDs:

1) Python 3.X assumes local platform's Unicode encoding default
for header/footer inserts and the output page: change as needed.
   ==> addressed and resolved in 1.3, per version notes above

2) This script would probably run on Python 2.X too if the
viewer_thumbs module didn't import "tkinter" (a book legacy).
   ==> but no longer as is, as of 1.3's Unicode enhancements

3) Styling of the table is open ended.  For example, a <table>
style "border-spacing: 4px;" may help avoid names running into
each other, though the default (2px?) seems adequate.
   ==> addressed in version 1.4 with extra CSS styling

4) A ".." parent link is not generated in automatic subfolder
lists; should it be (the parent may or may not have images)?

5) Creating the index file in the image folder might preclude
use of some page-generation tools (see trnpix template copies).

6) The reused viewer_thumbs module skips non-image files by simply
catching errors; this could use Python's mimetypes module instead.
See PyPhotos' newer version of this module, which uses mimetypes 
as described; alas, it's a fork.  Here's the code's online link:
    learning-python.com/
       pygadgets-products/unzipped/_PyPhoto/PIL/viewer_thumbs.py

7) Assuming UTF8 for all insert files' Unicode encoding is simple
(and also works for ASCII, as it's a UTF8 subset), but requires
either a minor script change or file conversions for differing
platform defaults; is this a significant usage factor?

8) This could generate a doctype and meta content-type tag in all
cases - not just for default headers - but that would limit doctype.
===========================================================================
"""

import os, sys, glob
import html, cgi, urllib.parse              # 1.3 text escapes

from viewer_thumbs import makeThumbs        # courtesy of the book PP4E
if sys.version[0] == 2: input = raw_input   # 2.X compatibility (but unused)

# 1.3: cgi.escape is subsumed by html.escape which was new in 3.2
html_escape = html.escape if hasattr(html, 'escape') else cgi.escape
url_escape  = urllib.parse.quote


#--------------------------------------------------------------------------
# Configure: manual settings (for now)
#--------------------------------------------------------------------------

# output folder/file names
THUMBS = 'thumbs'                           # created subfolder (changeable)
INDEX  = 'index'                            # created page (or 'default'/'home')

# code generation options
listSubfolders = True                       # auto folder list? (or via header)
uniformColumns = True                       # same-width columns? (else content)
spanFullWindow = False                      # stretch table to window's width?
useViewPort    = True                       # add mobile-friendly viewport? [1.4]

# 1.3: non-default Unicode encodings
#
# for insert: None=default platform encoding, and 'utf8' also handles ascii
# for output: use a real encoding name (not None), but 'utf8' likely suffices
#
insertEncoding = 'UTF-8'                    # or None, 'latin1', 'utf16',...
outputEncoding = 'UTF-8'                    # changeable, but utf8 is general


#--------------------------------------------------------------------------
# Configure: console inputs, Enter=default
#--------------------------------------------------------------------------

# y or n => remove any exiting thumb files?
cleanFirst = input('Clean thumbs? ').lower() in ['y', 'yes']

# int => fixed row size, irrespective of window
reply = input('Thumbs per row? ')
thumbsPerRow = int(reply) if reply else 4    # 5=>4: less horiz scrolling [1.4]

# (int, int) => max (x, y) pixels limit, preserving original aspect ratio
reply = input('Thumb max size? ')
thumbMaxSize = eval(reply) if reply else (100, 100)

# str => images folder path: images, header/footer, output
imageDir  = input('Images folder path? ') or ''                # default='.'


# the output page created in images folder
indexPath  = os.path.join(imageDir, INDEX + '.html')

# optional inserts in images folder, else generic text
headerPath = os.path.join(imageDir, 'HEADER.html')    
footerPath = os.path.join(imageDir, 'FOOTER.html')


#--------------------------------------------------------------------------
# Make thumbnails in image folder subdir (via viewer_thumbs)
#--------------------------------------------------------------------------

if cleanFirst:
    for thumbpath in glob.glob(os.path.join(imageDir, THUMBS, '*')):
        print('Cleaning %s' % thumbpath)
        os.remove(thumbpath)

makeThumbs(imageDir, size=thumbMaxSize, subdir=THUMBS)    # from PP4E


#--------------------------------------------------------------------------
# Create links text for each thumb->image
#--------------------------------------------------------------------------

imglinks = []
for thumbname in sorted(os.listdir(os.path.join(imageDir, THUMBS))):
    target = url_escape(thumbname, encoding=outputEncoding)
    source = url_escape(THUMBS + '/' + thumbname, encoding=outputEncoding)
    link = ('<A href="%s"><img src="%s" border=1></A>' %
                  (target, source))                   
    imglinks.append((html_escape(thumbname), link))   # use Unix / for web!


#--------------------------------------------------------------------------
# Create links for any subfolders here
#--------------------------------------------------------------------------

sublinks = []
for item in sorted(os.listdir(imageDir)):
    if item != THUMBS and not item.startswith('_'):   # skip thumbnails and '_x'
        itempath = os.path.join(imageDir, item)       # uses local path here
        if os.path.isdir(itempath):
            escsub = html_escape(item, quote=True)
            target = url_escape(item, encoding=outputEncoding)
            sublinks.append('<A href="%s">%s</A>' % (target, escsub))


#--------------------------------------------------------------------------
# Generate full web page in images folder
#--------------------------------------------------------------------------

# generate UTF8 content tag for non-ASCII use cases
doctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'

contype = '<meta http-equiv="content-type" content="text/html; charset=%s">'
contype = contype % outputEncoding

# [1.4]
viewmeta = '<meta name="viewport" content="width=device-width, initial-scale=1.0">'

# don't assume Unicode default
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('<!-- end custom header -->\n'.upper())
else:
    foldername = os.path.basename(imageDir)
    escfoldername = html_escape(foldername, quote=True)
    print(doctype)
    print('<html><head>')
    print(contype)
    if useViewPort:
        print(viewmeta)         
    print('<title>Index of %s</title>'
          '\n</head>\n<body>\n'
          '<h1>Index of image folder "%s"</h1>' % ((escfoldername,) * 2))
    print('<!-- end default header -->\n'.upper())

# subfolders bullet list (skip other content)
if sublinks and listSubfolders:
    print('<p><b>Subfolders here:</b><div style="margin-bottom: 30px;"><p><ul>')
    for link in sublinks:
          linkstyle = 'style="margin-bottom: 5px;"'    # add space for mobile [1.4]
          print('<li %s>%s' % (linkstyle, link))       # add space below list [1.4]
    print('</div></ul></p>')
          
# thumb links table 
print('<p><hr><p>')
print('<div style="overflow-x: auto;">')     # table autoscroll on small screens [1.4]
tabstyle = ''                                # window scroll breaks mobile widgets
if spanFullWindow:
    tabstyle = ' style="width: 100%;"'       # expand table to entire window
print('<table%s>' %  tabstyle)               # not used: "table-layout:fixed;"

# thumb links cells
while imglinks:
    row, imglinks = imglinks[:thumbsPerRow], imglinks[thumbsPerRow:]
    print('<tr>')
    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('<td %s>%s<p %s>%s</p></td>' % (colstyle, link, labstyle, escname))
    print('</tr>')
    #print('<tr><tr>')   # drop in [1.4]: use css as needed

print('</table></div></p><hr>')

# footer section
if os.path.exists(footerPath):
    # assume HTML-safe (pre-escaped), explicit Unicode
    print('\n<!-- start custom footer -->'.upper())
    insert = open(footerPath, 'r', encoding=insertEncoding)
    print(insert.read())
else:
    print('\n<!-- start default footer -->'.upper())
    print('<p><i>This page was generated by '
          '<A HREF="http://learning-python.com/thumbspage.html">thumbspage.py'
          '</A></i></p>')
    print('</body></html>')

sys.stdout.close()



[Home] Books Programs Blog Python Author Training Search Email ©M.Lutz