Merge pull request #434 from dsschult/non_image_files
plugin to handle non-image or video files
This commit is contained in:
@@ -126,6 +126,11 @@ Nomedia plugin
|
||||
|
||||
.. automodule:: sigal.plugins.nomedia
|
||||
|
||||
Non-media Files plugin
|
||||
======================
|
||||
|
||||
.. automodule:: sigal.plugins.nonmedia_files
|
||||
|
||||
Upload to S3 plugin
|
||||
===================
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
189
sigal/plugins/nonmedia_files.py
Normal file
189
sigal/plugins/nonmedia_files.py
Normal 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)
|
||||
@@ -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')
|
||||
|
||||
@@ -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',
|
||||
|
||||
BIN
tests/sample/pictures/nonmedia_files/dummy.pdf
Normal file
BIN
tests/sample/pictures/nonmedia_files/dummy.pdf
Normal file
Binary file not shown.
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user