forked from varia/distribusi
refactoring and thumbnailer
This commit is contained in:
parent
4cb03d1131
commit
c0b40c7d30
@ -6,83 +6,78 @@ from distribusi.distribusi import distribusify
|
||||
|
||||
def build_argparser():
|
||||
parser = argparse.ArgumentParser(
|
||||
"""
|
||||
"""
|
||||
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
|
||||
automatic index functions featured in several popular web servers.
|
||||
distribusi works by traversing the file system and directory hierarchy to
|
||||
automatically list all the files in the directory, detect the file types
|
||||
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(
|
||||
'-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(
|
||||
'-v', '--verbose', help="Print verbose debug output", action="store_true"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-t',
|
||||
'--thumbnail',
|
||||
"-t",
|
||||
"--thumbnail",
|
||||
help="Generate 450x450 thumbnails for images",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-n',
|
||||
'--no-template',
|
||||
"-n",
|
||||
"--no-template",
|
||||
help="Don't use the template to output html",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-nf',
|
||||
'--no-filenames',
|
||||
help="Don't add file names to listing",
|
||||
"-c",
|
||||
"--captions",
|
||||
help="Adds image captions based on EXIF metadata",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-c',
|
||||
'--captions',
|
||||
help="Adds image captions based on EXIF metadata, requires 'exiftool'",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-r',
|
||||
'--remove-index',
|
||||
"-r",
|
||||
"--remove-index",
|
||||
help="Recursively removes all instances of index.html that have been previously made by distribusi",
|
||||
action="store_true")
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-e',
|
||||
'--exclude-directory',
|
||||
"-e",
|
||||
"--exclude-directory",
|
||||
help="Exclude one or multiple directories from indexing",
|
||||
nargs="*",
|
||||
metavar='DIR')
|
||||
metavar="DIR",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-f',
|
||||
'--force',
|
||||
"-f",
|
||||
"--force",
|
||||
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(
|
||||
'--no-hidden',
|
||||
help="Exclude hidden directories",
|
||||
action="store_true")
|
||||
"--no-hidden", help="Exclude hidden directories", action="store_true"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--menu-with-index',
|
||||
"--menu-with-index",
|
||||
help="Append index.html to menu items to aid navigation",
|
||||
action="store_true")
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
import base64
|
||||
import os
|
||||
import subprocess
|
||||
from io import BytesIO
|
||||
|
||||
import magic
|
||||
from PIL import Image
|
||||
|
||||
from exif import Image as ExifImage
|
||||
from distribusi.page_template import html_footer, html_head
|
||||
from distribusi.mappings import CODE_TYPES, FILE_TYPES, SUB_TYPES
|
||||
|
||||
@ -14,62 +12,49 @@ MIME_TYPE = magic.Magic(mime=True)
|
||||
|
||||
def caption(image):
|
||||
try:
|
||||
process = subprocess.Popen(
|
||||
['exiftool', '-Comment', image], stdout=subprocess.PIPE)
|
||||
out, err = process.communicate()
|
||||
with open(image, "rb") as image_file:
|
||||
exif_image = ExifImage(image_file)
|
||||
caption = exif_image.communication
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print('Do you have exiftool installed?')
|
||||
try:
|
||||
caption = out.decode("utf-8").split(": ", 1)[1]
|
||||
except Exception as e:
|
||||
caption = ''
|
||||
print(e)
|
||||
caption = ""
|
||||
return caption
|
||||
|
||||
|
||||
def thumbnail(image, name, args):
|
||||
def thumbnail(full_path_image, name, args):
|
||||
if full_path_image.endswith("_thumbnail.jpg"):
|
||||
return
|
||||
try:
|
||||
size = (450, 450)
|
||||
im = Image.open(image)
|
||||
im.thumbnail(size)
|
||||
thumbnail_image = Image.open(full_path_image)
|
||||
thumbnail_image.thumbnail(size)
|
||||
|
||||
if (im.mode == 'RGBA'):
|
||||
bg = Image.new('RGBA', im.size, (255,255,255))
|
||||
composite = Image.alpha_composite(bg, im)
|
||||
im=composite.convert('RGB')
|
||||
if thumbnail_image.mode == "RGBA":
|
||||
bg = Image.new("RGBA", im.size, (255, 255, 255))
|
||||
composite = Image.alpha_composite(bg, thumbnail_image)
|
||||
thumbnail_image = composite.convert("RGB")
|
||||
|
||||
output = BytesIO()
|
||||
im.save(output, format='JPEG')
|
||||
im_data = output.getvalue()
|
||||
data_url = base64.b64encode(im_data).decode()
|
||||
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)
|
||||
image_filename_no_ext = os.path.splitext(full_path_image)[0]
|
||||
thumbnail_filename = f"{image_filename_no_ext}_thumbnail.jpg"
|
||||
thumbnail_image.save(thumbnail_filename, format="JPEG")
|
||||
return os.path.basename(thumbnail_filename)
|
||||
except Exception as e:
|
||||
print('Thumbnailer:', e)
|
||||
return "<figure><a href='{}'><img src='{}'></a><figcaption>{}</figcaption></figure>".format(name, name, name)
|
||||
print("Thumbnailer:", e)
|
||||
return
|
||||
|
||||
|
||||
def div(args, type_, subtype, tag, name):
|
||||
id_name = name.split('.')[0].replace(' ', '_')
|
||||
if args.no_filenames:
|
||||
filename = ''
|
||||
else:
|
||||
filename = '<span class="filename">{}</span>'.format(name)
|
||||
def format_div(args, type_, subtype, tag, name):
|
||||
id_name = name.split(".")[0].replace(" ", "_")
|
||||
filename = f'<span class="filename">{name}</span>'
|
||||
|
||||
if 'image' in type_:
|
||||
if "image" in type_:
|
||||
html = '<div id="{}" class="{}">{}</div>'
|
||||
elif 'pdf' in subtype:
|
||||
html = '<div id="{}" class="{}">{}' + filename + '</div>'
|
||||
elif 'dir' in type_ or 'html' in subtype or 'unkown-file' in subtype:
|
||||
elif "pdf" in subtype:
|
||||
html = '<div id="{}" class="{}">{}' + filename + "</div>"
|
||||
elif "dir" in type_ or "html" in subtype or "unkown-file" in subtype:
|
||||
html = '<div id="{}" class="{}">{}</div>'
|
||||
else:
|
||||
html = '<div id="{}" class="{}">{}' + filename + '</div>'
|
||||
html = '<div id="{}" class="{}">{}' + filename + "</div>"
|
||||
|
||||
return html.format(id_name, subtype, tag)
|
||||
|
||||
@ -80,140 +65,159 @@ def check_distribusi_index(args, index):
|
||||
"""
|
||||
|
||||
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():
|
||||
return True
|
||||
else:
|
||||
if args.verbose:
|
||||
print(index, 'not generated by distribusi, skipping')
|
||||
print(index, "not generated by distribusi, skipping")
|
||||
return False
|
||||
elif args.force:
|
||||
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 args.style:
|
||||
fs = open(args.style, "r")
|
||||
style = fs.read()
|
||||
file_style = open(args.style, "r")
|
||||
style = file_style.read()
|
||||
styled_html_head = html_head % style
|
||||
else:
|
||||
styled_html_head = html_head % ''
|
||||
f.write(styled_html_head)
|
||||
styled_html_head = html_head % ""
|
||||
index_file.write(styled_html_head)
|
||||
|
||||
for line in html:
|
||||
f.write(line + '\n')
|
||||
index_file.write(line + "\n")
|
||||
|
||||
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
|
||||
for root, dirs, files in os.walk(directory):
|
||||
html = []
|
||||
|
||||
if args.exclude_directory:
|
||||
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]
|
||||
|
||||
if args.no_hidden:
|
||||
dirs = list(filter(lambda d: not d.startswith('.'), dirs))
|
||||
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)
|
||||
dirs, files = remove_hidden_files_and_folders(dirs, files)
|
||||
|
||||
if args.remove_index:
|
||||
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)
|
||||
remove_index_html(root, files)
|
||||
|
||||
if args.verbose:
|
||||
print("Generating directory listing for", root)
|
||||
|
||||
for name in sorted(files):
|
||||
if "index.html" in name:
|
||||
continue
|
||||
|
||||
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)
|
||||
|
@ -1,14 +1,15 @@
|
||||
CODE_TYPES = ['x-c', 'x-shellscript', 'x-python']
|
||||
CODE_TYPES = ["x-c", "x-shellscript", "x-python"]
|
||||
|
||||
FILE_TYPES = {
|
||||
'image': '<figure><img class="image" src="{}"><figcaption>{}</figcaption></figure>',
|
||||
'text': '<a href="{}" class="text">{}</a>',
|
||||
'video': ('<video controls>' '<source src="{}"></video>'),
|
||||
'audio': ('<audio controls class="audio">' '<source src="{}"></audio>'),
|
||||
"image": '<figure><img class="image" src="{}"><figcaption>{}</figcaption></figure>',
|
||||
"text": '<a href="{}" class="text">{}</a>',
|
||||
"video": ("<video controls>" '<source src="{}"></video>'),
|
||||
"audio": ('<audio controls class="audio">' '<source src="{}"></audio>'),
|
||||
}
|
||||
|
||||
SUB_TYPES = {
|
||||
'pdf': (
|
||||
'<object data="{}" class="pdf" type="application/pdf">'
|
||||
'<embed src="{}" type="application/pdf" /></object>')
|
||||
"pdf": (
|
||||
'<object data="{}" class="pdf" type="application/pdf">'
|
||||
'<embed src="{}" type="application/pdf" /></object>'
|
||||
)
|
||||
}
|
||||
|
30
setup.py
30
setup.py
@ -1,28 +1,28 @@
|
||||
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()
|
||||
|
||||
setup(
|
||||
name='distribusi',
|
||||
version='0.0.10',
|
||||
url='https://git.vvvvvvaria.org/varia/distribusi',
|
||||
license='GPLv3',
|
||||
author='Varia',
|
||||
name="distribusi",
|
||||
version="0.0.11",
|
||||
url="https://git.vvvvvvaria.org/varia/distribusi",
|
||||
license="GPLv3",
|
||||
author="Varia",
|
||||
description=(
|
||||
'Distribusi is a content management system for '
|
||||
'the web that produces static pages based on '
|
||||
'the file system.'
|
||||
"Distribusi is a content management system for "
|
||||
"the web that produces static pages based on "
|
||||
"the file system."
|
||||
),
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
packages=find_packages(exclude=['tests']),
|
||||
long_description_content_type="text/markdown",
|
||||
packages=find_packages(exclude=["tests"]),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
platforms='any',
|
||||
platforms="any",
|
||||
install_requires=dependencies,
|
||||
entry_points={'console_scripts': ['distribusi = distribusi.cli:cli_entrypoint']},
|
||||
classifiers=['Programming Language :: Python :: 3', 'Environment :: Console'],
|
||||
entry_points={"console_scripts": ["distribusi = distribusi.cli:cli_entrypoint"]},
|
||||
classifiers=["Programming Language :: Python :: 3", "Environment :: Console"],
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user