refactoring and thumbnailer

This commit is contained in:
crunk 2024-06-23 20:35:06 +02:00
parent 4cb03d1131
commit c0b40c7d30
4 changed files with 215 additions and 215 deletions

View File

@ -6,83 +6,78 @@ from distribusi.distribusi import distribusify
def build_argparser(): def build_argparser():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
""" """
distribusi is a content management system for the web that produces static distribusi is a content management system for the web that produces static
index pages based on folders in the files system. It is inspired by the index pages based on folders in the files system. It is inspired by the
automatic index functions featured in several popular web servers. automatic index functions featured in several popular web servers.
distribusi works by traversing the file system and directory hierarchy to distribusi works by traversing the file system and directory hierarchy to
automatically list all the files in the directory, detect the file types automatically list all the files in the directory, detect the file types
and providing them with relevant html classes and tags for easy styling. and providing them with relevant html classes and tags for easy styling.
""") """
parser.add_argument(
'-d', '--directory', help="Select which directory to distribute", default="."
) )
parser.add_argument( parser.add_argument(
'-s', '--style', help="Select a CSS style sheet to include" "-d", "--directory", help="Select which directory to distribute", default="."
)
parser.add_argument("-s", "--style", help="Select a CSS style sheet to include")
parser.add_argument(
"-v", "--verbose", help="Print verbose debug output", action="store_true"
) )
parser.add_argument( parser.add_argument(
'-v', '--verbose', help="Print verbose debug output", action="store_true" "-t",
) "--thumbnail",
parser.add_argument(
'-t',
'--thumbnail',
help="Generate 450x450 thumbnails for images", help="Generate 450x450 thumbnails for images",
action="store_true", action="store_true",
) )
parser.add_argument( parser.add_argument(
'-n', "-n",
'--no-template', "--no-template",
help="Don't use the template to output html", help="Don't use the template to output html",
action="store_true", action="store_true",
) )
parser.add_argument( parser.add_argument(
'-nf', "-c",
'--no-filenames', "--captions",
help="Don't add file names to listing", help="Adds image captions based on EXIF metadata",
action="store_true", action="store_true",
) )
parser.add_argument( parser.add_argument(
'-c', "-r",
'--captions', "--remove-index",
help="Adds image captions based on EXIF metadata, requires 'exiftool'",
action="store_true",
)
parser.add_argument(
'-r',
'--remove-index',
help="Recursively removes all instances of index.html that have been previously made by distribusi", help="Recursively removes all instances of index.html that have been previously made by distribusi",
action="store_true") action="store_true",
)
parser.add_argument( parser.add_argument(
'-e', "-e",
'--exclude-directory', "--exclude-directory",
help="Exclude one or multiple directories from indexing", help="Exclude one or multiple directories from indexing",
nargs="*", nargs="*",
metavar='DIR') metavar="DIR",
)
parser.add_argument( parser.add_argument(
'-f', "-f",
'--force', "--force",
help="Force whether distribusi overwrites or removes instances of index.html not generated by distribusi, use at own risk!", help="Force whether distribusi overwrites or removes instances of index.html not generated by distribusi, use at own risk!",
action="store_true") action="store_true",
)
parser.add_argument( parser.add_argument(
'--no-hidden', "--no-hidden", help="Exclude hidden directories", action="store_true"
help="Exclude hidden directories", )
action="store_true")
parser.add_argument( parser.add_argument(
'--menu-with-index', "--menu-with-index",
help="Append index.html to menu items to aid navigation", help="Append index.html to menu items to aid navigation",
action="store_true") action="store_true",
)
return parser return parser

View File

@ -1,11 +1,9 @@
import base64 import base64
import os import os
import subprocess
from io import BytesIO
import magic import magic
from PIL import Image from PIL import Image
from exif import Image as ExifImage
from distribusi.page_template import html_footer, html_head from distribusi.page_template import html_footer, html_head
from distribusi.mappings import CODE_TYPES, FILE_TYPES, SUB_TYPES from distribusi.mappings import CODE_TYPES, FILE_TYPES, SUB_TYPES
@ -14,62 +12,49 @@ MIME_TYPE = magic.Magic(mime=True)
def caption(image): def caption(image):
try: try:
process = subprocess.Popen( with open(image, "rb") as image_file:
['exiftool', '-Comment', image], stdout=subprocess.PIPE) exif_image = ExifImage(image_file)
out, err = process.communicate() caption = exif_image.communication
except Exception as e: except Exception as e:
print(e) print(e)
print('Do you have exiftool installed?') caption = ""
try:
caption = out.decode("utf-8").split(": ", 1)[1]
except Exception as e:
caption = ''
print(e)
return caption return caption
def thumbnail(image, name, args): def thumbnail(full_path_image, name, args):
if full_path_image.endswith("_thumbnail.jpg"):
return
try: try:
size = (450, 450) size = (450, 450)
im = Image.open(image) thumbnail_image = Image.open(full_path_image)
im.thumbnail(size) thumbnail_image.thumbnail(size)
if (im.mode == 'RGBA'): if thumbnail_image.mode == "RGBA":
bg = Image.new('RGBA', im.size, (255,255,255)) bg = Image.new("RGBA", im.size, (255, 255, 255))
composite = Image.alpha_composite(bg, im) composite = Image.alpha_composite(bg, thumbnail_image)
im=composite.convert('RGB') thumbnail_image = composite.convert("RGB")
output = BytesIO() image_filename_no_ext = os.path.splitext(full_path_image)[0]
im.save(output, format='JPEG') thumbnail_filename = f"{image_filename_no_ext}_thumbnail.jpg"
im_data = output.getvalue() thumbnail_image.save(thumbnail_filename, format="JPEG")
data_url = base64.b64encode(im_data).decode() return os.path.basename(thumbnail_filename)
if args.captions:
cap = caption(image)
else:
cap = name
return (
"<figure><a href='{}'><img class='thumbnail' src='data:image/jpg;base64,{}'></a><figcaption>{}</figcaption></figure>"
).format(name, data_url, cap)
except Exception as e: except Exception as e:
print('Thumbnailer:', e) print("Thumbnailer:", e)
return "<figure><a href='{}'><img src='{}'></a><figcaption>{}</figcaption></figure>".format(name, name, name) return
def div(args, type_, subtype, tag, name): def format_div(args, type_, subtype, tag, name):
id_name = name.split('.')[0].replace(' ', '_') id_name = name.split(".")[0].replace(" ", "_")
if args.no_filenames: filename = f'<span class="filename">{name}</span>'
filename = ''
else:
filename = '<span class="filename">{}</span>'.format(name)
if 'image' in type_: if "image" in type_:
html = '<div id="{}" class="{}">{}</div>' html = '<div id="{}" class="{}">{}</div>'
elif 'pdf' in subtype: elif "pdf" in subtype:
html = '<div id="{}" class="{}">{}' + filename + '</div>' html = '<div id="{}" class="{}">{}' + filename + "</div>"
elif 'dir' in type_ or 'html' in subtype or 'unkown-file' in subtype: elif "dir" in type_ or "html" in subtype or "unkown-file" in subtype:
html = '<div id="{}" class="{}">{}</div>' html = '<div id="{}" class="{}">{}</div>'
else: else:
html = '<div id="{}" class="{}">{}' + filename + '</div>' html = '<div id="{}" class="{}">{}' + filename + "</div>"
return html.format(id_name, subtype, tag) return html.format(id_name, subtype, tag)
@ -80,140 +65,159 @@ def check_distribusi_index(args, index):
""" """
if not args.force: if not args.force:
with open(index, 'r') as f: with open(index, "r") as f:
if '<meta name="generator" content="distribusi" />' in f.read(): if '<meta name="generator" content="distribusi" />' in f.read():
return True return True
else: else:
if args.verbose: if args.verbose:
print(index, 'not generated by distribusi, skipping') print(index, "not generated by distribusi, skipping")
return False return False
elif args.force: elif args.force:
return True return True
def write_index(args,index, html, html_head, html_footer):
with open(index, 'w') as f: def write_index(args, index, html, html_head, html_footer):
with open(index, "w") as index_file:
if not args.no_template: if not args.no_template:
if args.style: if args.style:
fs = open(args.style, "r") file_style = open(args.style, "r")
style = fs.read() style = file_style.read()
styled_html_head = html_head % style styled_html_head = html_head % style
else: else:
styled_html_head = html_head % '' styled_html_head = html_head % ""
f.write(styled_html_head) index_file.write(styled_html_head)
for line in html: for line in html:
f.write(line + '\n') index_file.write(line + "\n")
if not args.no_template: if not args.no_template:
f.write(html_footer) index_file.write(html_footer)
def handle_text_files(name, full_path):
if name.endswith(".html") or subtype == "html":
subtype = "html"
# what types of text files to expand
tag = '<section id="{}">{}</section>'.format(
name, open(full_path).read()
)
elif subtype in CODE_TYPES or name.endswith(".txt"):
# if the plain text is code,
# which types do we wrap in pre-tags?
tag = "<pre>" + open(full_path).read() + "</pre>"
else:
subtype = subtype + " unkown-file"
tag = "<a href='{}'>{}</a>"
# a = FILE_TYPES[type_]
return subtype, tag
def handle_image_files(name, full_path, args):
if args.thumbnail:
caption = ""
thumbnail_filename = thumbnail(full_path, name, args)
if thumbnail_filename is None:
return
if args.captions:
caption = caption(full_path)
return f"<figure><a href='{name}'><img class='thumbnail' src='{thumbnail_filename}'></a><figcaption>{caption}</figcaption></figure>"
return FILE_TYPES[type_].format(name, caption)
def remove_index_html(root, files):
index = os.path.join(root, "index.html")
if "index.html" in files:
try:
if check_distribusi_index(args, index):
if args.verbose:
print("Removing index.html from", root)
os.remove(index)
except Exception as e:
print(e)
return
def remove_hidden_files_and_folders(dirs, files):
dirs = list(filter(lambda d: not d.startswith("."), dirs))
files = list(filter(lambda f: not f.startswith("."), files))
return dirs, files
def distribusify(args, directory): # noqa def distribusify(args, directory): # noqa
for root, dirs, files in os.walk(directory): for root, dirs, files in os.walk(directory):
html = []
if args.exclude_directory: if args.exclude_directory:
if args.verbose: if args.verbose:
print('Excluding directory:', ", ".join(args.exclude_directory)) print("Excluding directory:", ", ".join(args.exclude_directory))
dirs[:] = [d for d in dirs if d not in args.exclude_directory] dirs[:] = [d for d in dirs if d not in args.exclude_directory]
if args.no_hidden: if args.no_hidden:
dirs = list(filter(lambda d: not d.startswith('.'), dirs)) dirs, files = remove_hidden_files_and_folders(dirs, files)
files = list(filter(lambda f: not f.startswith('.'), files))
dirs.sort()
files.sort()
if not args.remove_index:
html = []
if args.verbose:
print('Generating directory listing for', root)
for name in sorted(files):
if 'index.html' not in name:
full_path = os.path.join(root, name)
mime = MIME_TYPE.from_file(full_path)
# example: MIME plain/text becomes 'type' plain 'subtype' text
type_, subtype = mime.split('/')
caption = name
if args.verbose:
print('Found', name, 'as', mime)
if type_ in FILE_TYPES:
a = FILE_TYPES[type_].format(name, caption)
# expansion for different kind of text files
if type_ == 'text':
if name.endswith('.html') or subtype == 'html':
subtype = 'html'
# what types of text files to expand
a = '<section id="{}">{}</section>'.format(name, open(full_path).read())
elif subtype in CODE_TYPES or name.endswith('.txt'):
# if the plain text is code,
# which types do we wrap in pre-tags?
a = "<pre>" + open(full_path).read() + "</pre>"
else:
subtype = subtype+' unkown-file'
a = "<a href='{}'>{}</a>"
# a = FILE_TYPES[type_]
if type_ == 'image':
if args.thumbnail:
a = thumbnail(full_path, name, args)
if args.no_filenames:
caption = ""
a = FILE_TYPES[type_].format(name, caption)
if args.captions:
caption = caption(full_path)
a = FILE_TYPES[type_].format(name, caption)
if subtype in SUB_TYPES:
a = SUB_TYPES[subtype]
if type_ not in FILE_TYPES and subtype not in SUB_TYPES:
# catch exceptions not yet defined in FILE_TYPES or SUB_TYPES
a = "<a href='{}'>{}</a>"
if args.verbose:
message = 'not in list of file types, adding as plain href: \n'
print(type_, subtype, message, name)
subtype = subtype + ' unkown-file'
a = a.replace('{}', name)
html.append(div(args, type_, subtype, a, name))
if root != directory:
if args.menu_with_index:
html.append('<a href="../index.html">../</a>')
else:
html.append('<a href="../">../</a>')
for name in dirs:
if args.menu_with_index:
a = "<a href='{}/index.html'>{}</a>".replace('{}', name)
else:
a = "<a href='{}'>{}/</a>".replace('{}', name)
html.insert(0, div(args, 'dir', 'dir', a, 'folder'))
index = os.path.join(root, 'index.html')
if os.path.exists(index):
if check_distribusi_index(args, index):
write_index(args,index,html, html_head, html_footer)
elif not os.path.exists(index):
write_index(args,index,html, html_head, html_footer)
if args.remove_index: if args.remove_index:
index = os.path.join(root, 'index.html') remove_index_html(root, files)
if 'index.html' in files:
try: if args.verbose:
if check_distribusi_index(args, index): print("Generating directory listing for", root)
if args.verbose:
print('Removing index.html from', root) for name in sorted(files):
os.remove(index) if "index.html" in name:
except Exception as e: continue
print(e)
if name.endswith("_thumbnail.jpg"):
continue
full_path = os.path.join(root, name)
mime = MIME_TYPE.from_file(full_path)
type_, subtype = mime.split("/")
caption = name
if args.verbose:
print("Found", name, "as", mime)
if type_ in FILE_TYPES:
match type_:
case "text":
subtype, tag = handle_text_files(name, full_path)
case "image":
tag = handle_image_files(name, full_path, args)
if tag is None:
continue
case _:
tag = FILE_TYPES[type_].format(name, caption)
if subtype in SUB_TYPES:
tag = SUB_TYPES[subtype]
if type_ not in FILE_TYPES and subtype not in SUB_TYPES:
# catch exceptions not yet defined in FILE_TYPES or SUB_TYPES
tag = "<a href='{}'>{}</a>"
if args.verbose:
message = "not in list of file types, adding as plain href: \n"
print(type_, subtype, message, name)
subtype = subtype + " unkown-file"
tag = tag.replace("{}", name)
html.append(format_div(args, type_, subtype, tag, name))
if root != directory:
if args.menu_with_index:
html.append('<a href="../index.html">../</a>')
else:
html.append('<a href="../">../</a>')
for name in sorted(dirs):
if args.menu_with_index:
tag = "<a href='{}/index.html'>{}</a>".replace("{}", name)
else:
tag = "<a href='{}'>{}/</a>".replace("{}", name)
html.insert(0, format_div(args, "dir", "dir", tag, "folder"))
index = os.path.join(root, "index.html")
if os.path.exists(index):
if check_distribusi_index(args, index):
write_index(args, index, html, html_head, html_footer)
elif not os.path.exists(index):
write_index(args, index, html, html_head, html_footer)

View File

@ -1,14 +1,15 @@
CODE_TYPES = ['x-c', 'x-shellscript', 'x-python'] CODE_TYPES = ["x-c", "x-shellscript", "x-python"]
FILE_TYPES = { FILE_TYPES = {
'image': '<figure><img class="image" src="{}"><figcaption>{}</figcaption></figure>', "image": '<figure><img class="image" src="{}"><figcaption>{}</figcaption></figure>',
'text': '<a href="{}" class="text">{}</a>', "text": '<a href="{}" class="text">{}</a>',
'video': ('<video controls>' '<source src="{}"></video>'), "video": ("<video controls>" '<source src="{}"></video>'),
'audio': ('<audio controls class="audio">' '<source src="{}"></audio>'), "audio": ('<audio controls class="audio">' '<source src="{}"></audio>'),
} }
SUB_TYPES = { SUB_TYPES = {
'pdf': ( "pdf": (
'<object data="{}" class="pdf" type="application/pdf">' '<object data="{}" class="pdf" type="application/pdf">'
'<embed src="{}" type="application/pdf" /></object>') '<embed src="{}" type="application/pdf" /></object>'
)
} }

View File

@ -1,28 +1,28 @@
from setuptools import find_packages, setup from setuptools import find_packages, setup
dependencies = ['pillow >= 10.3.0', 'python-magic >= 0.4.15, < 1.0'] dependencies = ["pillow >= 10.3.0", "python-magic >= 0.4.15, < 1.0", "exif >= 1.6.0"]
with open('README.md', 'r') as handle: with open("README.md", "r") as handle:
long_description = handle.read() long_description = handle.read()
setup( setup(
name='distribusi', name="distribusi",
version='0.0.10', version="0.0.11",
url='https://git.vvvvvvaria.org/varia/distribusi', url="https://git.vvvvvvaria.org/varia/distribusi",
license='GPLv3', license="GPLv3",
author='Varia', author="Varia",
description=( description=(
'Distribusi is a content management system for ' "Distribusi is a content management system for "
'the web that produces static pages based on ' "the web that produces static pages based on "
'the file system.' "the file system."
), ),
long_description=long_description, long_description=long_description,
long_description_content_type='text/markdown', long_description_content_type="text/markdown",
packages=find_packages(exclude=['tests']), packages=find_packages(exclude=["tests"]),
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,
platforms='any', platforms="any",
install_requires=dependencies, install_requires=dependencies,
entry_points={'console_scripts': ['distribusi = distribusi.cli:cli_entrypoint']}, entry_points={"console_scripts": ["distribusi = distribusi.cli:cli_entrypoint"]},
classifiers=['Programming Language :: Python :: 3', 'Environment :: Console'], classifiers=["Programming Language :: Python :: 3", "Environment :: Console"],
) )