Merge pull request #434 from dsschult/non_image_files

plugin to handle non-image or video files
This commit is contained in:
Simon Conseil
2021-07-06 21:32:17 +02:00
committed by GitHub
8 changed files with 262 additions and 10 deletions

View File

@@ -126,6 +126,11 @@ Nomedia plugin
.. automodule:: sigal.plugins.nomedia
Non-media Files plugin
======================
.. automodule:: sigal.plugins.nonmedia_files
Upload to S3 plugin
===================

View File

@@ -173,9 +173,13 @@ def build(source, destination, debug, verbose, quiet, force, config, theme,
return '{} {}s{}'.format(stats[_type], _type, opt)
if not quiet:
print('Done, processed {} and {} in {:.2f} seconds.'
.format(format_stats('image'), format_stats('video'),
time.time() - start_time))
stats_str = ''
types = sorted(set(t.rsplit('_',1)[0] for t in stats))
for t in types[:-1]:
stats_str += '{} and '.format(format_stats(t))
stats_str += '{}'.format(format_stats(types[-1]))
print('Done, processed {} in {:.2f} seconds.'
.format(stats_str, time.time() - start_time))
def init_plugins(settings):

View File

@@ -42,7 +42,7 @@ from PIL import Image as PILImage
from . import image, signals, video
from .image import get_exif_tags, get_image_metadata, get_size, process_image
from .settings import get_thumb
from .settings import Status, get_thumb
from .utils import (Devnull, cached_property, check_or_create_dir, copy,
get_mime, is_valid_html5_video, read_markdown,
url_from_path)
@@ -340,15 +340,22 @@ class Album:
for f in filenames:
ext = splitext(f)[1]
media = None
if ext.lower() in settings['img_extensions']:
media = Image(f, self.path, settings)
elif ext.lower() in settings['video_extensions']:
media = Video(f, self.path, settings)
else:
continue
self.medias_count[media.type] += 1
medias.append(media)
# Allow modification of the media, including overriding the class
# type for the media.
result = signals.album_file.send(self, filename=f, media=media)
for recv, ret in result:
if ret is not None:
media = ret
if media:
self.medias_count[media.type] += 1
medias.append(media)
signals.album_initialized.send(self)
@@ -835,8 +842,23 @@ class Gallery:
def process_file(media):
processor = process_image if media.type == 'image' else process_video
return processor(media)
processor = None
if media.type == 'image':
processor = process_image
elif media.type == 'video':
processor = process_video
# Allow overriding of the processor
result = signals.process_file.send(media, processor=processor)
for recv, ret in result:
if ret is not None:
processor = ret
if processor:
return processor(media)
else:
logging.warning('Processor not found for media %s', media.path)
return Status.FAILURE
def worker(args):

View File

@@ -0,0 +1,189 @@
"""Plugin to index non-media files.
This plugin will copy the files into the build tree and generate generic
thumbnails for the files. In-browser previews will likely fail, and
it is up to the theme to provide correct support for downloads.
Settings available as dictionary in ``nonmedia_files_options``:
- ``ext_as_thumb``: Enable simple thumbnail showing ext.
Default to ``True``
- ``ignore_ext``: List of file extensions to ignore.
Default to ``[".md"]``
- ``thumb_bg_color``: Background color for thumbnail. Accepts (r, g, b) tuple.
Default to white ``(255, 255, 255)``.
- ``thumb_font``: Name or path to font file.
Default to ``None`` to use the PIL built-in font.
- ``thumb_font_color``: Font color for thumbnail. Accepts (r, g, b) tuple.
Default to black ``(0, 0, 0)``.
- ``thumb_font_size``: Font size for thumbnail. Must select a font to apply.
Default to ``40``.
.. note:: Thumbnails are generated using the file extension text on a
background color. It is highly recommended to select a font
(such as ``"FreeMono.ttf"``), since the default PIL font does not
respect font size.
"""
import logging
import os
from PIL import Image as PILImage
from PIL import ImageDraw, ImageFont
from pilkit.utils import save_image
from sigal import signals
from sigal import utils
from sigal.gallery import Media
from sigal.settings import Status
logger = logging.getLogger(__name__)
DEFAULT_CONFIG = {
'ext_as_thumb': True,
'ignore_ext': ['.md'],
'thumb_bg_color': (255, 255, 255),
'thumb_font': None,
'thumb_font_color': (0, 0, 0),
'thumb_font_size': 40,
}
COMMON_MIME_TYPES = {
'.azw': 'application/vnd.amazon.ebook',
'.csv': 'text/csv',
'.epub': 'application/epub+zip',
'.pdf': 'application/pdf',
'.svg': 'image/svg+xml',
'.txt': 'text/plain',
'.zip': 'application/zip',
}
def get_mime(ext):
if ext in COMMON_MIME_TYPES:
return COMMON_MIME_TYPES[ext]
return 'application/octet-stream'
class NonMedia(Media):
"""Gather all informations on a non-media file."""
type = 'nonmedia'
def __init__(self, filename, path, settings):
super().__init__(filename, path, settings)
self.thumb_name = os.path.splitext(self.thumb_name)[0] + '.jpg'
self.date = self._get_file_date()
self.mime = get_mime(self.src_ext)
logger.debug('mime type %s', self.mime)
@property
def thumbnail(self):
"""Path to the thumbnail image (relative to the album directory)."""
if not os.path.isfile(self.thumb_path):
kwargs = {}
plugin_settings = self.settings.get('nonmedia_files_options', {})
if plugin_settings.get('thumb_bg_color', None):
kwargs['bg_color'] = plugin_settings['thumb_bg_color']
if plugin_settings.get('thumb_font', None):
kwargs['font'] = plugin_settings['thumb_font']
if plugin_settings.get('thumb_font_color', None):
kwargs['font_color'] = plugin_settings['thumb_font_color']
if plugin_settings.get('thumb_font_size', None):
kwargs['font_size'] = plugin_settings['thumb_font_size']
generate_thumbnail(self.src_ext[1:].upper(), self.thumb_path,
self.settings['thumb_size'],
options=self.settings['jpg_options'],
**kwargs)
return super().thumbnail
def generate_thumbnail(text, outname, box,
bg_color=DEFAULT_CONFIG['thumb_bg_color'],
font=DEFAULT_CONFIG['thumb_font'],
font_color=DEFAULT_CONFIG['thumb_font_color'],
font_size=DEFAULT_CONFIG['thumb_font_size'],
options=None):
"""Create a thumbnail image."""
kwargs = {}
if font:
kwargs['font'] = ImageFont.truetype(font, font_size)
if font_color:
kwargs['fill'] = font_color
img = PILImage.new("RGB", box, bg_color)
anchor = (box[0] // 2, box[1] // 2)
d = ImageDraw.Draw(img)
logger.info(f"kwargs: {kwargs}")
d.text(anchor, text, anchor='mm', **kwargs)
outformat = 'JPEG'
logger.info('Save thumnail image: %s (%s)', outname, outformat)
save_image(img, outname, outformat, options=options, autoconvert=True)
def process_nonmedia(media):
"""Process a non-media file: copy and create thumbnail."""
logger.info('Processing non-media file: %s', media.dst_filename)
settings = media.settings
plugin_settings = settings.get('nonmedia_files_options', {})
try:
utils.copy(media.src_path, media.dst_path,
symlink=settings['orig_link'])
except Exception:
if logger.getEffectiveLevel() == logging.DEBUG:
raise
else:
return Status.FAILURE
if plugin_settings.get('ext_as_thumb', DEFAULT_CONFIG['ext_as_thumb']):
logger.info('plugin_settings: %r', plugin_settings)
try:
kwargs = {}
if plugin_settings.get('thumb_font', None):
kwargs['font'] = plugin_settings['thumb_font']
if plugin_settings.get('thumb_bg_color', None):
kwargs['bg_color'] = plugin_settings['thumb_bg_color']
if plugin_settings.get('thumb_font_color', None):
kwargs['font_color'] = plugin_settings['thumb_font_color']
generate_thumbnail(
media.src_ext[1:].upper(),
media.thumb_path,
settings['thumb_size'],
options=settings['jpg_options'],
**kwargs
)
except Exception:
if logger.getEffectiveLevel() == logging.DEBUG:
raise
else:
return Status.FAILURE
return Status.SUCCESS
def album_file(album, filename, media=None):
if not media:
ext = os.path.splitext(filename)[1]
ext_ignore = album.settings.get('nonmedia_files_options', {}).get(
'ignore_ext', DEFAULT_CONFIG['ignore_ext'])
if ext in ext_ignore:
logger.info('Ignoring non-media file: %s', filename)
else:
logger.info('Registering non-media file: %s', filename)
return NonMedia(filename, album.path, album.settings)
def process_file(media, processor=None):
if media.type == 'nonmedia':
return process_nonmedia
def register(settings):
signals.album_file.connect(album_file)
signals.process_file.connect(process_file)

View File

@@ -9,3 +9,5 @@ media_initialized = signal('media_initialized')
albums_sorted = signal('albums_sorted')
medias_sorted = signal('medias_sorted')
before_render = signal('before_render')
album_file = signal('album_file')
process_file = signal('process_file')

View File

@@ -273,6 +273,7 @@ ignore_files = []
# 'sigal.plugins.feeds',
# 'sigal.plugins.media_page',
# 'sigal.plugins.nomedia',
# 'sigal.plugins.nonmedia_files',
# 'sigal.plugins.upload_s3',
# 'sigal.plugins.watermark',
# 'sigal.plugins.zip_gallery',
@@ -299,6 +300,16 @@ ignore_files = []
# 'ask_password': False
# }
# Settings for nonmedia_files plugin.
# nonmedia_files_options = {
# 'ext_as_thumb': True,
# 'ignore_ext': ['.md'],
# 'thumb_bg_color': (255, 255, 255),
# 'thumb_font': None, # name/path of font file
# 'thumb_font_color': (0, 0, 0),
# 'thumb_font_size': 40, # only applies when a font is selected
# }
# Settings for upload to s3 plugin
# upload_s3_options = {
# 'bucket': 'my-bucket',

Binary file not shown.

View File

@@ -27,3 +27,22 @@ def test_plugins(settings, tmpdir, disconnect_signals):
for file in files:
assert "ignore" not in file
def test_nonmedia_files(settings, tmpdir, disconnect_signals):
settings['destination'] = str(tmpdir)
settings['plugins'] += ['sigal.plugins.nonmedia_files']
init_plugins(settings)
gal = Gallery(settings)
gal.build()
outfile = os.path.join(settings['destination'],
'nonmedia_files', 'dummy.pdf')
assert os.path.isfile(outfile)
outthumb = os.path.join(settings['destination'],
'nonmedia_files', 'thumbnails', 'dummy.tn.jpg')
assert os.path.isfile(outthumb)