File: thumbspage/examples/__prior-version-items/__prior-versions-code/thumbspage-1.5-preImageScalingJS.py
#!/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 <body> - FOOTER.html should add any post-table content and close the <body> =========================================================================== 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 <hr> bug (see "CAVEATS" #6 ahead), by restyling the thumbs table with table borders instead of <hr>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 <hr> 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 <style> blocks, so that custom HEADER.html files need not include or link to styles for the generated table. Viewer pages use a <style> block, as they are not customizable (yet?). 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 UTF-8 Unicode encoding by default, with a content-type <meta> tag; ASCII content is unchanged, as it is a subset of UTF-8; other encodings may be used for the output file via setting outputEncoding in the code d) Load any header and footer inserts per UTF-8 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 UTF-8-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. =========================================================================== DEVELOPER 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. (Version 1.5 has grown more sophisticated, with image-viewer pages.) 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) Note that index-page styling must be all inline, to avoid asking custom HEADER.html files to code or link to this CSS code. 9) (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 --CLOSED-- 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) The reused viewer_thumbs module skips non-image files by simply catching errors; this could use Python's mimetypes module instead. ==> Addressed in version 1.5: incorporated the PyPhoto mods. ==> See PyPhoto's newer version of this module, which uses mimetypes as described; alas, it's a fork. Here's the newer code's online link: http://learning-python.com/ pygadgets-products/unzipped/_PyPhoto/PIL/viewer_thumbs.py 5) Relying on browser-specific display of full images isn't ideal, because such displays vary in scaling, background color, etc. ==> Addressed by version 1.5's image-viewer pages (see above). 6) Desktop Chrome botches <hr>s and <img> borders at some zoom levels: the former may be lighter at some page locations than others, and the latter may drop some sides altogether. This was first seen when odd Unicode characters were on-page, but can happen anywhere - at user-zoom levels at or below 90%, all bets are off on <hr> and <img>-border rendering. It also happens on both index pages and 1.5 viewer pages; the latter's CSS <img> borders may drop one or more sides at <= 90% zoom. This has been observed on desktop Chrome only, and in versions 66 and 68 on Mac OS and Windows. The only fix seems to be to not use <hr> or <img> borders - an extreme workaround. Styling <hr>s (e.g., style="height: 0.5px; background-color: black;") doesn't fix the issue at all zoom levels. This is clearly a (temporary?) browser bug, but unfortunate in this site's most widely-used browser. ==> Addressed by 1.5's restyle: table borders replace <hr>s. Chrome borders around <img>s still vanish at zoom <= 90%, but they are nice enough to keep, even if only 3 sided. --OPEN-- 7) A ".." parent link is not generated in automatic subfolder lists; should it be (the parent may or may not have images)? 8) Creating the index file in the image folder might preclude use of some page-generation tools (see trnpix template copies). 9) Assuming UTF-8 for all insert files' Unicode encoding is simple (and also works for ASCII, as it's a UTF-8 subset), but requires either a minor script change or file conversions for differing platform defaults; is this a significant usage factor? 10) This could generate a doctype and meta content-type tag in all cases - not just for default headers - but that would limit doctype. 11) Even after refactoring to functions in 1.5, there are still a lot of globals in this code; clean up more in next release? 12) Viewer-page image scaling in CSS is lousy - there's no way to adjust to changes in window aspect ratio well. For more details, see the "USAGE NOTE" above, and code notes below. JavaScript may help, but using Python to generate HTML that embeds both CSS and JavaScript seems a bit much for a simple Python image viewer... =========================================================================== """ # # Library tools # import os, sys, glob, re # [1.5] re for input tests import html, cgi, urllib.parse # [1.3] text escapes if sys.version[0] == 2: input = raw_input # 2.X compatibility (but unused!) from viewer_thumbs import makeThumbs # courtesy of the book PP4E from viewer_thumbs import isImageFileName # courtesy of standalone PyPhoto from viewer_thumbs import imageWideHigh # courtesy of Pillow/PIL def html_escape(text, **options): """ ----------------------------------------------------------------------- HTML escapes - for text inserted in HTML code [1.5] Both the html and cgi escaping functions take an additional 'quote' argument which defaults to True for html.escape, but False for cgi.escape; a True is needed iff the result is embedded in a quoted HTML attribute (e.g., <tag attr="%s">, but not <tag>%s</tag>). [1.3] cgi.escape is subsumed by html.escape which was new in 3.2. Both escape HTML-syntax text: 'good>&day' => 'good>&day'. ----------------------------------------------------------------------- """ escaper = html.escape if hasattr(html, 'escape') else cgi.escape return escaper(text, **options) def url_escape(link): """ ----------------------------------------------------------------------- URL escapes - for the text of inserted links [1.5] Always use UTF-8 here, not outputEncoding, per the following. [1.3]: The 'encoding' here is used only to preencode to bytes before applying escape replacements. The returned URL is an ASCII str string with '%xx' escapes; it's a URL-escaped format of Unicode-encoded text. How the resulting URL link is interpreted depends on the agent that unescapes it later, but general UTF-8 handles encoding of arbitrary content, and its unescaped (but still encoded) bytes are recognized everywhere that this script's results have been tested. Subtly, the encoding used for the while enclosing HTML page's content and declared in its <meta> tag (this script's 'outputEncoding') has nothing to do with the encoding used for embedded and escaped URL links (e.g., the HTML/URL encodings pair UTF-16/UTF-16 fails in browsers tested, but UTF-16/UTF-8 works correctly). In fact, UTF-8 appears to be required for URLs per standards docs, which makes urllib's alternative encoding option seem a bit dubious: https://tools.ietf.org/html/rfc3986#section-2.5 (older) https://tools.ietf.org/html/rfc3987#section-6.4 (newer) Tool examples: >>> ord('☞'), hex(ord('☞')) # code points (9758, '0x261e') >>> '☞'.encode('utf8'), '☞'.encode('utf16') # encoded bytes (b'\xe2\x98\x9e', b'\xff\xfe\x1e&') >>> from urllib.parse import quote >>> quote('http://a+b&c☞', encoding='utf8') # encode + escape 'http%3A//a%2Bb%26c%E2%98%9E' >>> quote('http://a+b&c☞', encoding='utf16') '%FF%FEh%00t%00t%00p%00%3A%00/%00/%00a%00%2B%00b%00%26%00c%00%1E%26' Other ideas: it's possible to skip urllib's Unicode encoding step by calling its quote_from_bytes(), but this just passes the buck - it requires a manually encoded bytes. URLs might also be embedded in HTML pages using the whole-page Unicode encoding, with only HTML escapes, or with no escapes (e.g., url_escape = lambda link: link, or url_escape = lambda link: html_escape(link, quote=True)); this almost works for UTF-16, but some pathological filename links fail. ----------------------------------------------------------------------- """ return urllib.parse.quote(link, encoding='UTF-8') #========================================================================== # CONFIGURE, PART 1: manual settings for rarely-changed options #========================================================================== # 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) # [1.5] now always on, for table borders # spanFullWindow = False # stretch table to window's width? # recent additions useViewPort = True # add mobile-friendly viewport? [1.4] caseSensOrder = True # index/nav order case sensitive? [1.5] thumbsBgColor = '#f5f5f5' # thumbs index table (was 'white') [1.5] viewerBgColor = 'black' # viewer pages background color [1.5] # # 1.3: use explicit Unicode encodings (not 'locale' platform defaults) # # insertEncoding # is used for header/footer file loads; # None=default platform encoding, 'utf8' also handles ascii; # # outputEncoding # is used for generated pages' content and <meta> tags (only); # use a real encoding name (not None), 'UTF-8' works for all; # insertEncoding = 'UTF-8' # or None, 'latin1', 'utf16',... outputEncoding = 'UTF-8' # changeable, but utf8 is general #========================================================================== # CONFIGURE, PART 2: console inputs for per-run options, enter=default #========================================================================== # Tools [1.5] def ask(prompt, hint, default): return input('%s [%s] (enter=%s)? ' % (prompt, hint, default)) def askbool(prompt): return ask(prompt, 'y or n', 'n').lower() in ['y', 'yes'] def askint(prompt, hint, default): reply = ask(prompt, hint, default) return int(reply) if reply else default def askeval(prompt, hint, default, require=None): reply = ask(prompt, hint, default) if reply and require: # else eval() mildly dangerous [1.5] assert re.match(require, reply), 'Invalid input "%s"' % reply return eval(reply) if reply else default # Inputs # y or n => remove any exiting thumb files? cleanFirst = askbool('Clean thumbs folder') # int => fixed row size, irrespective of window thumbsPerRow = askint('Thumbs per row', 'int', 4) # 5->4 so less scrolling [1.4] # (int, int) => max (x, y) pixels limit, preserving original aspect ratio require = '\(?[0-9]+\s*,\s*[0-9]+\)?' # 2-tuple, parens optional thumbMaxSize = askeval('Thumb max size', 'x, y', (100, 100), require) # str => images folder path: images, header/footer, output imageDir = ask('Images folder path', '. or dir', '.') # default = cwd # y or n => create image viewer pages? [1.5] useViewerPages = askbool('Use image-viewer pages') # Calcs # don't make a thumbs folder if input dir bad [1.5] assert os.path.isdir(imageDir), 'Invalid image folder' # 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) #========================================================================== def makeThumbnails(imageDir): """ ----------------------------------------------------------- Reuse the (now modified) thumbnail generator from PP4E. Its [(imgname, thumbobj)] return value is unused here: os.listdir() is run later to collect thumb names, and thumb objects are not used (building pages, not GUIs). ----------------------------------------------------------- """ if cleanFirst: # this cleans viewer pages too [1.5] for thumbpath in glob.glob(os.path.join(imageDir, THUMBS, '*')): print('Cleaning: %s' % thumbpath) os.remove(thumbpath) makeThumbs(imageDir, size=thumbMaxSize, subdir=THUMBS) #========================================================================== # GENERATE full index web page in images folder #========================================================================== def orderedListing(dirpath, casesensitive=caseSensOrder): """ ---------------------------------------------------------------- [1.5] A platform- and filesystem-neutral directory listing, which is case sensitive by default. Called out here because order must agree between index and view (navigation) pages. Uppercase matters by default: assumed to be more important. The os.listdir() order matters only on the build machine, not server (pages built here are static), but varies widely: on Mac OS, HFS is case-insensitive, but APFS is nearly random. The difference, on APFS ("ls" yields the second of these): >>> os.listdir('.') ['LA2.png', 'NYC.png', 'la1.png', '2018-x.png', 'nyc-more.png'] >>> sorted(os.listdir('.')) ['2018-x.png', 'LA2.png', 'NYC.png', 'la1.png', 'nyc-more.png'] >>> sorted(os.listdir('.'), key=str.lower) ['2018-x.png', 'la1.png', 'LA2.png', 'nyc-more.png', 'NYC.png'] ---------------------------------------------------------------- """ if casesensitive: return sorted(os.listdir(dirpath)) else: return sorted(os.listdir(dirpath), key=str.lower) def formatImageLinks(imageDir, styleImgThumbs): """ ---------------------------------------------------------------- Format index-page links text for each thumb. When a thumb is clicked, open either the raw image or a 1.5 view/navigate page. ---------------------------------------------------------------- """ imglinks = [] for thumbname in orderedListing(os.path.join(imageDir, THUMBS)): if not isImageFileName(thumbname): # skip prior viewer-page files if not cleaned [1.5] continue if not useViewerPages: # click opens raw image in . (1.4) target = url_escape(thumbname) else: # click opens viewer page in thumbs/ [1.5] viewpage = thumbname + '.html' target = url_escape(THUMBS + '/' + viewpage) # index page uses image in thumbs/ source = url_escape(THUMBS + '/' + thumbname) link = ('<A href="%s">\n\t<img src="%s" style="%s"></A>' % (target, source, styleImgThumbs)) imglinks.append((html_escape(thumbname), link)) # use Unix / for web! return imglinks def formatSubfolderLinks(imageDir): """ ---------------------------------------------------------------- Format index-page links text for any and all subfolders in the images folder. On link click, open folder or its index.html. ---------------------------------------------------------------- """ sublinks = [] for item in orderedListing(imageDir): # uniform ordering [1.5] if item != THUMBS and not item.startswith('_'): # skip thumbnails and '_*' # valid name itempath = os.path.join(imageDir, item) # uses local path here if os.path.isdir(itempath): # folder: make link escsub = html_escape(item) target = url_escape(item) sublinks.append('<A href="%s">%s</A>' % (target, escsub)) return sublinks # # Content defs: HTML/CSS constants # # [1.5] workaround vanishing <hr> bug on desktop Chrome at zoom <= 90% styleTableThumbs = """ /* not used: table-layout:fixed; */ background-color: %s; /* for fun (former 'white') */ width: 100%%; /* expand table+borderlines */ margin-top: 5px; margin-bottom: 5px; /* above/below borderlines */ padding-top: 25px; padding-bottom: 5px; /* under/over borderlines */ border-top: 1px solid black; /* manual lines, _not_ <hr>s: */ border-bottom: 1px solid black; /* chrome botches <hr> at zoom <= 90%% */ """ % thumbsBgColor # configurable, default=light grey # [1.5] Chrome botches these at zoom <= 90% too, but keep: still nice styleImgThumbs = 'border: 1px solid black;' # formerly: html border=1 # generate UTF-8 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]: mobile friendly viewmeta = '<meta name="viewport" content="width=device-width, initial-scale=1.0">' # [1.5]: default-header font for index pages (same in viewer pages) indexfont = '<style>body {font-family: Arial, Helvetica, sans-serif;}</style>' 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('<!-- end custom header -->'.upper()) else: foldername = os.path.basename(imageDir) escfoldername = html_escape(foldername) print(doctype) print('<html><head>') print(contype) if useViewPort: print(viewmeta) print(indexfont) print('<title>Index of %s</title>' '\n</head>\n<body>\n' '<h1>Index of image folder "%s"</h1>' % ((escfoldername,) * 2)) print('<!-- end default header -->'.upper()) # subfolders bullet list (skip other content) if sublinks and listSubfolders: print('\n<!-- start subfolder links -->'.upper()) print('<p><b>Subfolders here:</b><div style="margin-bottom: 30px;"><p><ul>') for link in sublinks: linkstyle = 'style="margin-bottom: 6px;"' # 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('\n<!-- start thumbs table -->'.upper()) print('<p>') # drop <hr> print('<div style="overflow-x: auto;">') # table autoscroll on small screens [1.4] # whole-window scroll breaks mobile widgets # [1.5] styled top/bottom borders, not <hr> print('<table style="%s">' % styleTableThumbs) # 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>\n\t%s\n\t<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>') # drop <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() # 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 = """<!DOCTYPE HTML> <html><head> <!-- generated by thumbspage: learning-python.com/thumbspage.html --> <!-- unicode and mobile friendly --> <meta http-equiv="content-type" content="text/html; charset=%(ENCODING)s"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> /* css in html expanded and generated by python (!) */ * { font-family: Arial, Helvetica, sans-serif; background-color: %(BGCOLOR)s; color: white; } img { display: block; /* kill bottom space */ /*vertical-align: text-bottom; /* but not quite this... */ } .navdiv { width: 100%%; position: fixed; bottom: 0; overflow-x: auto; } .navtable { width: 100%%; border-collapse: collapse; table-layout: fixed; } .navlink { display: block; /* nicer links through css */ text-decoration: none; text-align: center; } .navlink:hover { text-decoration: underline; font-style: italic; } .navlink:active { font-style: italic; } /* add horizontal padding for large fonts in Android Firefox */ .navtable td { padding-right: 10px; /* for all <td> nested in a class=navtable */ } /* smaller mobile devices: scale up toolbar links and no hover */ @media screen and (max-device-width: 640px) { .navtable { height: 1.25em; /* for 0..640 pixel screens only */ } .navlink { font-size: 1.25em; /* go large, for touch */ } .navlink:hover { text-decoration: none; /* else underline/italic may get stuck on */ font-style: normal; } } /* don't upscale text in landscape mode on iOS Safari */ @media screen and (max-device-width: 640px) { html { -webkit-text-size-adjust: 100%%; /* webkit browsers (old chrome too) */ } } </style> <title>%(IMAGENAME)s</title> </head><body> <!-- filename: possibly long, <h1> too big --> <div style="overflow-x: auto;"> <P style="text-align: center; margin-top: 4px; margin-bottom: 10px;"> %(IMAGENAME)s </p></div> <!-- image: scaled to fit window/viewport (not href: too busy) --> <div style="border: 1px solid white;"> <p align=center style="margin: 0px;"> <img src="%(IMAGEPATH)s" style="width: %(IMAGEWIDE)s; height: %(IMAGEHIGH)s;"> </p></div> <!-- buttons: probably too small to overflow viewport, but... --> <div class=navdiv> <p> <table class=navtable> <tr> <td><a class=navlink href="%(PREVPAGE)s">Prev</a></td> <td><a class=navlink href="%(NEXTPAGE)s">Next</a></td> <td><a class=navlink href="../index.html">Index</a></td> <td><a class=navlink href="%(IMAGEPATH)s">Raw</a></td> </tr></table> </p> <!-- the /p adds space below links bar (oddly) --> </div> </body></html> """ 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