Git Product home page Git Product logo

image-process's Introduction

Image Process: A Plugin for Pelican

Build Status PyPI Version Downloads License

Image Process is a plugin for Pelican, a static site generator written in Python.

Image Process let you automate the processing of images based on their class attribute. Use this plugin to minimize the overall page weight and to save you a trip to Gimp or Photoshop each time you include an image in your post.

Image Process also makes it easy to create responsive images using the HTML5 srcset attribute and <picture> tag. It does this by generating multiple derivative images from one or more sources.

Image Process will not overwrite your original images.

Installation

The easiest way to install Image Process is via Pip. This will also install the required dependencies automatically.

python -m pip install pelican-image-process

As long as you have not explicitly added a PLUGINS setting to your Pelican settings file, then the newly-installed plugin should be automatically detected and enabled. Otherwise, you must add image_process to your existing PLUGINS list. For more information, please see the documentation regarding How to Use Plugins.

You will then need to configure your desired transformations (see Usage below) and add the appropriate class to images you want processed.

Usage

Image Process scans your content for <img> tags with special classes. It then maps the classes to a set of image processing instructions, computes new images, and modifies HTML code according to the instructions.

Define Transformations

The first step in using this module is to define some image transformations in your Pelican configuration file. Transformations are defined in the IMAGE_PROCESS dictionary, mapping a transformation name to a list of operations. There are three kinds of transformations: image replacement, responsive image, and picture set.

Image Replacement

The simplest image processing will replace the original image by a new, transformed image computed from the original. You may use this, for example, to ensure that all images are of the same size, or to compute a thumbnail from a larger image:

IMAGE_PROCESS = {
    "article-image": ["scale_in 300 300 True"],
    "thumb": ["crop 0 0 50% 50%", "scale_out 150 150 True", "crop 0 0 150 150"],
}

These transformations tell Image Process to transform the image referred to by the src attribute of an <img> according to the list of operations specified, and replace the src attribute with the URL of the transformed image.

For consistency with other types of transformations described below, there is an alternative syntax for the processing instructions:

IMAGE_PROCESS = {
    "thumb": {
        "type": "image",
        "ops": ["crop 0 0 50% 50%", "scale_out 150 150 True", "crop 0 0 150 150"],
    },
    "article-image": {
        "type": "image",
        "ops": ["scale_in 300 300 True"],
    },
}

To apply image replacement to the images in your articles, you must add to them the special class image-process-<transform>, in which <transform> is the ID of the transformation you wish to apply.

Let's say you have defined the transformation described above. To get your image processed, it needs to have the right CSS class:

<img class="image-process-article-image" src="/images/pelican.jpg"/>

This can be produced in Markdown with:

![](/images/pelican.png){: .image-process-article-image}

In reStructuredText, use the :class: attribute of the image or the figure directive:

.. image:: /images/pelican.png
   :class: image-process-article-image
.. figure:: /images/pelican.png
    :class: image-process-article-image

โš ๏ธ Warning:

The reStructuredText reader will convert underscores (_) to dashes (-) in class names. To make sure everything runs smoothly, do not use underscores in your transformation names.

Responsive Images

You can use Image Process to automatically generate a set of images that will be selected for display by browsers according to the viewport width or according to the device resolution. To accomplish this, Image Process will add a srcset attribute (and maybe a media and a sizes attribute) to the <img> tag.

HTML5 supports two types of responsive image sets. The first one is device-pixel-ratio-based, selecting higher resolution images for higher resolution devices; the second one is viewport-based, selecting images according to the viewport size. You can read more about HTML5 responsive images for a gentle introduction to the srcset and <picture> syntaxes.

To tell Image Process to generate a responsive image, add a responsive-image transformation to your your IMAGE_PROCESS dictionary, with the following syntax:

IMAGE_PROCESS = {
    "crisp": {
        "type": "responsive-image",
        "srcset": [
            ("1x", ["scale_in 800 600 True"]),
            ("2x", ["scale_in 1600 1200 True"]),
            ("4x", ["scale_in 3200 2400 True"]),
        ],
        "default": "1x",
    },
    "large-photo": {
        "type": "responsive-image",
        "sizes": (
            "(min-width: 1200px) 800px, "
            "(min-width: 992px) 650px, "
            "(min-width: 768px) 718px, "
            "100vw"
        ),
        "srcset": [
            ("600w", ["scale_in 600 450 True"]),
            ("800w", ["scale_in 800 600 True"]),
            ("1600w", ["scale_in 1600 1200 True"]),
        ],
        "default": "800w",
    },
}

The crisp transformation is an example of a transformation enabling device-pixel-ratio-based selection. The srcset is a list of tuples, each tuple containing the image description ("1x", "2x", etc.) and the list of operations to generate the derivative image from the original image (the original image is the value of the src attribute of the <img>). Image descriptions are hints about the resolution of the associated image and must have the suffix x. The default setting specifies the image to use to replace the src attribute of the image. This is the image that will be displayed by browsers that do not support the srcset syntax.

The large-photo transformation is an example of a transformation enabling viewport-based selection. The sizes contains a rule to compute the width of the displayed image from the width of the viewport. Once the browser knows the image width, it will select an image source from the srcset. The srcset is a list of tuple, each tuple containing the image description ("600w", "800w", etc.) and the list of operations to generate the derivative image from the original image (the original image is the value of the src attribute of the <img>). Image descriptions are hints about the width in pixels of the associated image and must have the suffix w. The default setting specifies the image to use to replace the src attribute of the image. This is the image that will be displayed by browsers that do not support the srcset syntax.

In the two examples above, the default setting is a string referring to one of the images in the srcset. However, the default value could also be a list of operations to generate a different derivative image.

To make the images in your article responsive, you must add to them the special class image-process-<transform>, in which <transform> is the ID of the transformation you wish to apply, exactly like you would do for the image replacement case, described above.

So, in HTML it should look like this:

<img class="image-process-large-photo" src="/images/pelican.jpg"/>

Which can be produced in Markdown with:

![](/images/pelican.jpg){: .image-process-large-photo}

In reStructuredText, use the :class: attribute of the image or the figure directive:

.. image:: /images/pelican.jpg
   :class: image-process-large-photo
.. figure:: /images/pelican.jpg
    :class: image-process-large-photo

Picture Set

Image Process can be used to generate the images used by a <picture> tag. The <picture> syntax allows for more flexibility in providing a choice of image to the browser. Again, you can read more about HTML5 responsive images for a gentle introduction to the srcset and <picture> syntaxes.

To tell Image Process to generate the images for a <picture>, add a picture entry to your IMAGE_PROCESS dictionary with the following syntax:

IMAGE_PROCESS = {
    "example-pict": {
        "type": "picture",
        "sources": [
            {
                "name": "default",
                "media": "(min-width: 640px)",
                "srcset": [
                    ("640w", ["scale_in 640 480 True"]),
                    ("1024w", ["scale_in 1024 683 True"]),
                    ("1600w", ["scale_in 1600 1200 True"]),
                ],
                "sizes": "100vw",
            },
            {
                "name": "source-1",
                "srcset": [
                    ("1x", ["crop 100 100 200 200"]),
                    ("2x", ["crop 100 100 300 300"]),
                ]
            },
        ],
        "default": ("default", "640w"),
    },
}

Each of the sources entries is very similar to the responsive image describe above. Here, each source must have a name, which will be used to find the URL of the original image for this source in your article. The source may also have a media, which contains a rule used by the browser to select the active source. The default setting specifies the image to use to replace the src attribute of the <img> inside the <picture>. This is the image that will be displayed by browsers that do not support the <picture> syntax. In this example, it will use the image 640w from the source default. A list of operations could have been specified instead of 640w.

To generate a responsive <picture> for the images in your articles, you must add to your article a pseudo <picture> tag that looks like this:

<picture>
    <source class="source-1" src="/images/pelican-closeup.jpg"></source>
    <img class="image-process-example-pict" src="/images/pelican.jpg"/>
</picture>

Each <source> tag maps a source name (the class attribute) to a file (the src attribute). The <img> must have the special class image-process- followed by the name of the transformation you wish to apply. The file referenced by the src attribute of the <img> will be used as the special default source in your transformation definition.

You can't produce this with pure Markdown and must instead resort to raw HTML.

In reStructuredText, however, you can also use the figure directive to generate a <picture>. The figure image file will be used as the special default source; other sources must be added in the legend section of the figure as image directives. The figure class must be image-process- followed by the name of the transformation you wish to apply, while the other images must have two classes: image-process and the name of the source they provide an image for:

.. figure:: /images/pelican.jpg
   :class: image-process-example-pict

    Test picture

    .. image:: /images/pelican-closeup.jpg
       :class: image-process source-1

The images in the legend section that are used as source for the <picture> will be removed from the image legend, so that they do not appear in your final article.

Transformations

Available operations for transformations are:

  • crop <top> <left> <right> <bottom>:

    Crop the image to the box (<left>, <top>)-(<right>, <bottom>). Values can be absolute (a number) or relative to the size of the image (a number followed by a percent sign %).

  • flip_horizontal:

    Flip the image horizontally.

  • flip_vertical:

    Flip the image vertically.

  • grayscale:

    Convert the image to grayscale.

  • resize <width> <height>:

    Resize the image. This operation does not preserve the image aspect ratio. Values can be absolute (a number) or relative to the size of the image (a number followed by a percent sign %).

  • rotate <degrees>:

    Rotate the image.

  • scale_in <width> <height> <upscale>:

    Resize the image. This operation preserves the image aspect ratio and the resulting image will be no larger than <width> x <height>. Values can be absolute (a number) or relative to the size of the image (a number followed by a percent sign %). If <upscale> is False, smaller images will not be enlarged.

  • scale_out <width> <height> <upscale>:

    Resize the image. This operation preserves the image aspect ratio and the resulting image will be no smaller than <width> x <height>. Values can be absolute (a number) or relative to the size of the image (a number followed by a percent sign %). If <upscale> is False, smaller images will not be enlarged.

  • blur:

    Apply the pillow.ImageFilter.BLUR filter to the image.

  • contour:

    Apply the pillow.ImageFilter.CONTOUR filter to the image.

  • detail:

    Apply the pillow.ImageFilter.DETAIL filter to the image.

  • edge_enhance:

    Apply the pillow.ImageFilter.EDGE_ENHANCE filter to the image.

  • edge_enhance_more:

    Apply the pillow.ImageFilter.EDGE_ENHANCE_MORE filter to the image.

  • emboss:

    Apply the pillow.ImageFilter.EMBOSS filter to the image.

  • find_edges:

    Apply the pillow.ImageFilter.FIND_EDGES filter to the image.

  • smooth:

    Apply the pillow.ImageFilter.SMOOTH filter to the image.

  • smooth_more:

    Apply the pillow.ImageFilter.SMOOTH_MORE filter to the image.

  • sharpen:

    Apply the pillow.ImageFilter.SHARPEN filter to the image.

You can also define your own operations; the only requirement is that your operation should be a callable object expecting a pillow.Image as its first parameter and it should return the transformed image:

def crop_face(image):
    """Detect face in image and crop around it."""
    # Fancy algorithm.
    return image

IMAGE_PROCESS = {
    "face-thumbnail": [crop_face, "scale_out 150 150 True"]
}

Additional Settings

Destination Directory

By default, the new images will be stored in a directory named derivative/<TRANSFORMATION_NAME> in the output folder at the same location as the original image. For example, if the original image is located in the content/images folder, the computed images will be stored in output/images/derivative/<TRANSFORMATION_NAME>. All the transformations are done in the output directory in order to avoid confusion with the source files or if we test multiple transformations. You can replace derivative by something else using the IMAGE_PROCESS_DIR setting in your Pelican settings file:

IMAGE_PROCESS_DIR = "derivees"

Force Image Processing

If the transformed image already exists and is newer than the original image, the plugin assumes that it should not re-compute it again. You can force the plugin to re-compute all images by setting IMAGE_PROCESS_FORCE to True in your Pelican configuration file.

IMAGE_PROCESS_FORCE = True

Selecting a HTML Parser

You may select the HTML parser which is used. The default is the built-in html.parser but you may also select html5lib or lxml by setting IMAGE_PROCESS_PARSER in your Pelican settings file. For example:

IMAGE_PROCESS_PARSER = "html5lib"

For details, refer to the BeautifulSoup documentation on parsers.

File Encoding

You may select a different file encoding to be used by BeautifulSoup as it opens your files. The default is utf-8.

IMAGE_PROCESS_ENCODING = "utf-8"

Copying EXIF Tags

You may ask Image Process to copy the EXIF tags from your original image to the transformed images. You must have exiftool installed.

IMAGE_PROCESS_COPY_EXIF_TAGS = True

Known Issues

Contributing

Contributions are welcome and much appreciated. Every little bit helps. You can contribute by improving the documentation, adding missing features, and fixing bugs. You can also help out by reviewing and commenting on existing issues.

To start contributing to this plugin, review the Contributing to Pelican documentation, beginning with the Contributing Code section.

Regenerating Test Images

If you need to regenerate the transformed images used by the test suite, there is a helper function to do this for you. From the Python REPL:

>>> from pelican.plugins.image_process.test_image_process import generate_test_images
>>> generate_test_images()
36 test images generated!

License

This project is licensed under the AGPL-3.0 license.

Pelican image in test data by Jon Sullivan. Published under a Creative Commons Public Domain license.

image-process's People

Contributors

alexandrenorman avatar botpub avatar catdog2 avatar cunhaax avatar dependabot[bot] avatar docent-net avatar ecno92 avatar gagath avatar ixday avatar justinmayer avatar kdeldycke avatar kernc avatar lucas-c avatar minchinweb avatar patrickfournier avatar pre-commit-ci[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

image-process's Issues

Markdown rendering image with {attach} attribute fails

Rendering the following part:

[picture]({attach}test.jpg "Title"){class="image-process-go-left"}

like in http://docs.getpelican.com/en/3.6.3/content.html#attaching-static-files

Fails:

CRITICAL: FileNotFoundError: [Errno 2] No such file or directory: '/newsletter/content/{attach}test.jpg'
Traceback (most recent call last):
  File "/home/xael/python.venvs/newsletter/bin/pelican", line 11, in <module>
    sys.exit(main())
  File "/home/xael/python.venvs/newsletter/lib/python3.4/site-packages/pelican/__init__.py", line 478, in main
    pelican.run()
  File "/home/xael/python.venvs/newsletter/lib/python3.4/site-packages/pelican/__init__.py", line 185, in run
    signals.finalized.send(self)
  File "/home/xael/python.venvs/newsletter/lib/python3.4/site-packages/blinker/base.py", line 267, in send
    for receiver in self.receivers_for(sender)]
  File "/home/xael/python.venvs/newsletter/lib/python3.4/site-packages/blinker/base.py", line 267, in <listcomp>
    for receiver in self.receivers_for(sender)]
  File "/newsletter/pelican-plugins/image_process/image_process.py", line 501, in process_images
    i = Image.open(image[0])
  File "/home/xael/python.venvs/newsletter/lib/python3.4/site-packages/PIL/Image.py", line 2249, in open
    fp = builtins.open(filename, "rb")
FileNotFoundError: [Errno 2] No such file or directory: '/newsletter/content/{attach}test.jpg'

Changing process_images:

-            i = Image.open(image[0])
+            i = Image.open(image[0].replace('{attach}', ''))

and

-            i.save(image[1])
+            i.save(image[1].replace('{attach}', ''))

doesn't really help because of the attach methodโ€ฆ file is copied in the same directory than html page and is not know at harvesting time.

Couldn't find a tree builder with the features you requested: xml

Hi everyone,

I tried to use this plugin for pelican and was not able to create the html.

On executing make html I got the following error:

CRITICAL: Couldn't find a tree builder with the features you requested: xml. Do you need to install a parser library?
Traceback (most recent call last):
  File "~/.virtualenvs/pelican/bin/pelican", line 8, in <module>
    sys.exit(main())
  File "~/.virtualenvs/pelican/lib/python3.9/site-packages/pelican/__init__.py", line 527, in main
    pelican.run()
  File "~/.virtualenvs/pelican/lib/python3.9/site-packages/pelican/__init__.py", line 125, in run
    p.generate_output(writer)
  File "~/.virtualenvs/pelican/lib/python3.9/site-packages/pelican/generators.py", line 685, in generate_output
    self.generate_feeds(writer)
  File "~/.virtualenvs/pelican/lib/python3.9/site-packages/pelican/generators.py", line 329, in generate_feeds
    writer.write_feed(
  File "~/.virtualenvs/pelican/lib/python3.9/site-packages/pelican/writers.py", line 165, in write_feed
    signals.feed_written.send(
  File "~/.virtualenvs/pelican/lib/python3.9/site-packages/blinker/base.py", line 266, in send
    return [(receiver, receiver(sender, **kwargs))
  File "~/.virtualenvs/pelican/lib/python3.9/site-packages/blinker/base.py", line 266, in <listcomp>
    return [(receiver, receiver(sender, **kwargs))
  File "~/.virtualenvs/pelican/lib/python3.9/site-packages/pelican/plugins/image_process/image_process.py", line 206, in harvest_feed_images
    soup = BeautifulSoup(f, "xml")
  File "~/.virtualenvs/pelican/lib/python3.9/site-packages/bs4/__init__.py", line 243, in __init__
    raise FeatureNotFound(
bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: xml. Do you need to install a parser library?

I used the version 2.0.0 from pip. beautifulsoup4 in version 4.9.3

If I change the Line

soup = BeautifulSoup(f, "xml")

from soup = BeautifulSoup(f, "xml") to soup = BeautifulSoup(f, "html.parser") everything seems to work fine.

Is the xml parser in the function the correct one or should it be the one from the settings?

parser = settings.get("IMAGE_PROCESS_PARSER", "html.parser")
soup = BeautifulSoup(f, parser)

If the xml parser is the correct one, shouldn't be there a dependency to lxml?

BTW: Thanks for this plugin!

Test failures with Pillow 10.3+

Beginning with Pillow 10.3.0, and also affecting the current 10.4.0 release, the following test failures have surfaced:

========================================================================== short test summary info ==========================================================================
FAILED pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path0-detail-transform_params10] - assert (320, 31, 792, 721) is None
FAILED pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path0-smooth_more-transform_params16] - assert (16, 0, 1016, 728) is None
FAILED pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path1-detail-transform_params10] - assert (319, 65, 740, 716) is None
FAILED pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path1-smooth_more-transform_params16] - assert (327, 72, 741, 706) is None

As a temporary measure until the source of the problem can be properly addressed, I took the liberty of adding an upward version bound for Pillow via 0231fac in #82.

"make publish" fails when the plugin attempts to process the first file with images

I'm trying to setup my first pelican based blog on a Fedora Linux system using pelican-4.8.0 with pelican-image-process-3.0.3.

If I run make html or make regenerate everything works great. However once I'm ready to generate the final production content with make publish it blows up on the first file that has image references with IsADirectoryError: [Errno 21] Is a directory: '/home/netllama/stuff/llamaland/content/'. That's literally the content directory with all of my markdown files, so I'm not at all sure why this is an issue.

I re-ran with --debug:

pelican --debug /home/netllama/stuff/llamaland/content -o /home/netllama/stuff/llamaland/output -s /home/netllama/stuff/llamaland/publishconf.py

and got the following output, but I don't understand why its failing and expecting the content directory to be anything other than a directory:

           INFO     Writing /home/netllama/stuff/llamaland/output/western-usa.html                                                                                                                                 writers.py:212
           DEBUG    [image_process] harvesting '/home/netllama/stuff/llamaland/output/western-usa.html'                                                                                                      image_process.py:266
           CRITICAL IsADirectoryError: [Errno 21] Is a directory: '/home/netllama/stuff/llamaland/content/'                                                                                                       __init__.py:566
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Traceback (most recent call last) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/__init__.py:562 in โ”‚
โ”‚ main                                                                                             โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   559 โ”‚   โ”‚   โ”‚   watcher = FileSystemWatcher(args.settings, Readers, settings)                  โ”‚
โ”‚   560 โ”‚   โ”‚   โ”‚   watcher.check()                                                                โ”‚
โ”‚   561 โ”‚   โ”‚   โ”‚   with console.status("Generating..."):                                          โ”‚
โ”‚ โฑ 562 โ”‚   โ”‚   โ”‚   โ”‚   pelican.run()                                                              โ”‚
โ”‚   563 โ”‚   except KeyboardInterrupt:                                                              โ”‚
โ”‚   564 โ”‚   โ”‚   logger.warning('Keyboard interrupt received. Exiting.')                            โ”‚
โ”‚   565 โ”‚   except Exception as e:                                                                 โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/__init__.py:127 in โ”‚
โ”‚ run                                                                                              โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   124 โ”‚   โ”‚                                                                                      โ”‚
โ”‚   125 โ”‚   โ”‚   for p in generators:                                                               โ”‚
โ”‚   126 โ”‚   โ”‚   โ”‚   if hasattr(p, 'generate_output'):                                              โ”‚
โ”‚ โฑ 127 โ”‚   โ”‚   โ”‚   โ”‚   p.generate_output(writer)                                                  โ”‚
โ”‚   128 โ”‚   โ”‚                                                                                      โ”‚
โ”‚   129 โ”‚   โ”‚   signals.finalized.send(self)                                                       โ”‚
โ”‚   130                                                                                            โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/generators.py:703  โ”‚
โ”‚ in generate_output                                                                               โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   700 โ”‚                                                                                          โ”‚
โ”‚   701 โ”‚   def generate_output(self, writer):                                                     โ”‚
โ”‚   702 โ”‚   โ”‚   self.generate_feeds(writer)                                                        โ”‚
โ”‚ โฑ 703 โ”‚   โ”‚   self.generate_pages(writer)                                                        โ”‚
โ”‚   704 โ”‚   โ”‚   signals.article_writer_finalized.send(self, writer=writer)                         โ”‚
โ”‚   705 โ”‚                                                                                          โ”‚
โ”‚   706 โ”‚   def refresh_metadata_intersite_links(self):                                            โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/generators.py:605  โ”‚
โ”‚ in generate_pages                                                                                โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   602 โ”‚   โ”‚                                                                                      โ”‚
โ”‚   603 โ”‚   โ”‚   # to minimize the number of relative path stuff modification                       โ”‚
โ”‚   604 โ”‚   โ”‚   # in writer, articles pass first                                                   โ”‚
โ”‚ โฑ 605 โ”‚   โ”‚   self.generate_articles(write)                                                      โ”‚
โ”‚   606 โ”‚   โ”‚   self.generate_period_archives(write)                                               โ”‚
โ”‚   607 โ”‚   โ”‚   self.generate_direct_templates(write)                                              โ”‚
โ”‚   608                                                                                            โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/generators.py:474  โ”‚
โ”‚ in generate_articles                                                                             โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   471 โ”‚   โ”‚   โ”‚   self.hidden_translations, self.hidden_articles                                 โ”‚
โ”‚   472 โ”‚   โ”‚   ):                                                                                 โ”‚
โ”‚   473 โ”‚   โ”‚   โ”‚   signals.article_generator_write_article.send(self, content=article)            โ”‚
โ”‚ โฑ 474 โ”‚   โ”‚   โ”‚   write(article.save_as, self.get_template(article.template),                    โ”‚
โ”‚   475 โ”‚   โ”‚   โ”‚   โ”‚     self.context, article=article, category=article.category,                โ”‚
โ”‚   476 โ”‚   โ”‚   โ”‚   โ”‚     override_output=hasattr(article, 'override_save_as'),                    โ”‚
โ”‚   477 โ”‚   โ”‚   โ”‚   โ”‚     url=article.url, blog=True)                                              โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/writers.py:269 in  โ”‚
โ”‚ write_file                                                                                       โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   266 โ”‚   โ”‚   โ”‚   # no pagination                                                                โ”‚
โ”‚   267 โ”‚   โ”‚   โ”‚   localcontext = _get_localcontext(                                              โ”‚
โ”‚   268 โ”‚   โ”‚   โ”‚   โ”‚   context, name, kwargs, relative_urls)                                      โ”‚
โ”‚ โฑ 269 โ”‚   โ”‚   โ”‚   _write_file(template, localcontext, self.output_path, name,                    โ”‚
โ”‚   270 โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   override_output)                                                   โ”‚
โ”‚   271                                                                                            โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/writers.py:216 in  โ”‚
โ”‚ _write_file                                                                                      โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   213 โ”‚   โ”‚   โ”‚                                                                                  โ”‚
โ”‚   214 โ”‚   โ”‚   โ”‚   # Send a signal to say we're writing a file with some specific                 โ”‚
โ”‚   215 โ”‚   โ”‚   โ”‚   # local context.                                                               โ”‚
โ”‚ โฑ 216 โ”‚   โ”‚   โ”‚   signals.content_written.send(path, context=localcontext)                       โ”‚
โ”‚   217 โ”‚   โ”‚                                                                                      โ”‚
โ”‚   218 โ”‚   โ”‚   def _get_localcontext(context, name, kwargs, relative_urls):                       โ”‚
โ”‚   219 โ”‚   โ”‚   โ”‚   localcontext = context.copy()                                                  โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/blinker/base.py:263 in     โ”‚
โ”‚ send                                                                                             โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   260 โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   '%s given' % len(sender))                                      โ”‚
โ”‚   261 โ”‚   โ”‚   else:                                                                              โ”‚
โ”‚   262 โ”‚   โ”‚   โ”‚   sender = sender[0]                                                             โ”‚
โ”‚ โฑ 263 โ”‚   โ”‚   return [(receiver, receiver(sender, **kwargs))                                     โ”‚
โ”‚   264 โ”‚   โ”‚   โ”‚   โ”‚   for receiver in self.receivers_for(sender)]                                โ”‚
โ”‚   265 โ”‚                                                                                          โ”‚
โ”‚   266 โ”‚   def has_receivers_for(self, sender):                                                   โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/blinker/base.py:263 in     โ”‚
โ”‚ <listcomp>                                                                                       โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   260 โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   '%s given' % len(sender))                                      โ”‚
โ”‚   261 โ”‚   โ”‚   else:                                                                              โ”‚
โ”‚   262 โ”‚   โ”‚   โ”‚   sender = sender[0]                                                             โ”‚
โ”‚ โฑ 263 โ”‚   โ”‚   return [(receiver, receiver(sender, **kwargs))                                     โ”‚
โ”‚   264 โ”‚   โ”‚   โ”‚   โ”‚   for receiver in self.receivers_for(sender)]                                โ”‚
โ”‚   265 โ”‚                                                                                          โ”‚
โ”‚   266 โ”‚   def has_receivers_for(self, sender):                                                   โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/plugins/image_proc โ”‚
โ”‚ ess/image_process.py:280 in harvest_images                                                       โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   277 โ”‚   โ”‚   context["IMAGE_PROCESS_COPY_EXIF_TAGS"] = False                                    โ”‚
โ”‚   278 โ”‚                                                                                          โ”‚
โ”‚   279 โ”‚   with open(path, "r+", encoding=context["IMAGE_PROCESS_ENCODING"]) as f:                โ”‚
โ”‚ โฑ 280 โ”‚   โ”‚   res = harvest_images_in_fragment(f, context)                                       โ”‚
โ”‚   281 โ”‚   โ”‚   f.seek(0)                                                                          โ”‚
โ”‚   282 โ”‚   โ”‚   f.truncate()                                                                       โ”‚
โ”‚   283 โ”‚   โ”‚   f.write(res)                                                                       โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/plugins/image_proc โ”‚
โ”‚ ess/image_process.py:354 in harvest_images_in_fragment                                           โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   351 โ”‚   โ”‚                                                                                      โ”‚
โ”‚   352 โ”‚   โ”‚   elif d["type"] == "responsive-image" and "srcset" not in img.attrs:                โ”‚
โ”‚   353 โ”‚   โ”‚   โ”‚   # srcset image specification.                                                  โ”‚
โ”‚ โฑ 354 โ”‚   โ”‚   โ”‚   build_srcset(img, settings, derivative)                                        โ”‚
โ”‚   355 โ”‚   โ”‚                                                                                      โ”‚
โ”‚   356 โ”‚   โ”‚   elif d["type"] == "picture":                                                       โ”‚
โ”‚   357 โ”‚   โ”‚   โ”‚   # Multiple source (picture) specification.                                     โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/plugins/image_proc โ”‚
โ”‚ ess/image_process.py:446 in build_srcset                                                         โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   443                                                                                            โ”‚
โ”‚   444 def build_srcset(img, settings, derivative):                                               โ”‚
โ”‚   445 โ”‚   path = compute_paths(img, settings, derivative)                                        โ”‚
โ”‚ โฑ 446 โ”‚   if not is_img_identifiable(path.source):                                               โ”‚
โ”‚   447 โ”‚   โ”‚   logger.warn(                                                                       โ”‚
โ”‚   448 โ”‚   โ”‚   โ”‚   "%s Skipping image %s that could not be identified by Pillow",                 โ”‚
โ”‚   449 โ”‚   โ”‚   โ”‚   LOG_PREFIX,                                                                    โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/plugins/image_proc โ”‚
โ”‚ ess/image_process.py:438 in is_img_identifiable                                                  โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   435                                                                                            โ”‚
โ”‚   436 def is_img_identifiable(img_filepath):                                                     โ”‚
โ”‚   437 โ”‚   try:                                                                                   โ”‚
โ”‚ โฑ 438 โ”‚   โ”‚   Image.open(img_filepath)                                                           โ”‚
โ”‚   439 โ”‚   โ”‚   return True                                                                        โ”‚
โ”‚   440 โ”‚   except (FileNotFoundError, UnidentifiedImageError):                                    โ”‚
โ”‚   441 โ”‚   โ”‚   return False                                                                       โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/PIL/Image.py:3092 in open  โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   3089 โ”‚   โ”‚   filename = fp                                                                     โ”‚
โ”‚   3090 โ”‚                                                                                         โ”‚
โ”‚   3091 โ”‚   if filename:                                                                          โ”‚
โ”‚ โฑ 3092 โ”‚   โ”‚   fp = builtins.open(filename, "rb")                                                โ”‚
โ”‚   3093 โ”‚   โ”‚   exclusive_fp = True                                                               โ”‚
โ”‚   3094 โ”‚                                                                                         โ”‚
โ”‚   3095 โ”‚   try:                                                                                  โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
IsADirectoryError: [Errno 21] Is a directory: '/home/netllama/stuff/llamaland/content/'

Post to PyPI

Would you consider posting this to PyPI? This would allow the plugin to be installed using pip, rather than requiring cloning the repo.

'make html' generates correct IMG, but 'make publish' does not

I'm running pelican-4.8.0 with pelican-image-process-3.0.3. My pelicanconf.py has:

IMAGE_PROCESS = {
    "large-photo": {
        "type": "responsive-image",
        "sizes": (
            "(min-width: 1600px) 1420px, "
            "(min-width: 1200px) 1020px, "
            "(min-width: 992px) 860px, "
            "(min-width: 768px) 748px, "
            "100vw"
        ),
        "srcset": [
            ("600w", ["scale_in 600 450 True"]),
            ("800w", ["scale_in 800 600 True"]),
            ("1600w", ["scale_in 1600 1200 True"]),
            ("2000w", ["scale_in 2000 1600 True"]),
        ],
        "default": "800w",
    },
}

and my markdown files have code similar to the following:

![](llamaland/img/2022/11/IMG_20221113_082400.jpg){: .image-process-large-photo}

When I run 'make html', the generated HTML has the correct expected code such as:

<img alt="" class="image-process-large-photo" sizes="(min-width: 1600px) 1420px, (min-width: 1200px) 1020px, (min-width: 992px) 860px, (min-width: 768px) 748px, 100vw" src="llamaland/img/2022/11/derivatives/large-photo/800w/IMG_20221113_082400.jpg" srcset="llamaland/img/2022/11/derivatives/large-photo/600w/IMG_20221113_082400.jpg 600w, llamaland/img/2022/11/derivatives/large-photo/800w/IMG_20221113_082400.jpg 800w, llamaland/img/2022/11/derivatives/large-photo/1600w/IMG_20221113_082400.jpg 1600w, llamaland/img/2022/11/derivatives/large-photo/2000w/IMG_20221113_082400.jpg 2000w"/>

However, when I run 'make publish' its as if the image-process plugin isn't used at all, and I end up with the full size images all the time. There's nothing in publishconf.py related to the plugin.

I'm confused why this isn't working correctly.

click through to full image

I can see that not every one want to host full size images but i would like users/views of my blog to be able to click through to the original.

Have i missed something in the config or is this much harder to do?

If its do able i would be happy to help but im not sure what the maintainers think is the best way to do this? if a doc fix is what is needed i am happy to help with that or to add some code.. but dont want to duplicate effort if some already knows how to do it

image_process relative URL issue?

I am using the latest version of image_process plugin with pelican 4.5.3, using Python 3.8.5 on OSX Catalina using bootstrap2 theme. After following the documentation I only get proper results when served locally not remotely. i.e. while:

![]({static}/photos/test.png)

would lead to correct URL / displayed image, both when served locally and on the remote server, the following line:

<img class="image-process-large-photo" src="/photos/test.png"/>

leads to a tag with relative URL

<img class="image-process-large-photo" sizes="(min-width: 1200px) 800px, (min-width: 992px) 650px, (min-width: 768px) 718px, 100vw" src="/photos/derivatives/large-photo/800w/test.png" srcset="/photos/derivatives/large-photo/600w/test.png 600w, /photos/derivatives/large-photo/800w/test.png 800w, /photos/derivatives/large-photo/1600w/test.png 1600w"/></p>

which is only locally visible, but on the remote server does not find it.

Is there a way to change this behavior?

thanks!

Add cache-busting of images

I would like to be able to "cache bust" my images, that is add a fragment of a hash to the filename as a final step in the processing, sorta like mv foo.jpg foo.$(shasum foo.jpg | head -c 8).jpg. I've taken a look at the source for this plugin, and don't think it would be too difficult to add. I would be willing to make a PR for this functionality if the maintainers think it belongs here.

RSS feeds images are not processed

While using this plugin I discovered that it was not processing the images that are shown in the RSS feed. While the required image sources and classes are available in de RSS source.

I already found out that articles and feeds have different signal. The one that is used in the current plugin only processes articles (HTML pages).

Hooking up the signal (signals.feed_write) for the feeds was not sufficient since the format of RSS and article contents is not 100% compatible with each other. So in order to process RSS feeds we probably need to change more than a few lines.

I think it's important to have support for RSS feeds since RSS feeds are often consumed by mobile devices with low bandwidth internet connections.

Unfortunately it's quite some work to get it well supported. So I'll leave it by a issue report for now.
Hopefully anyone else has a opportunity to work on this in the near future. ๐Ÿ˜„

Release v2.0.0 on PyPi

This is a ticket to keep track of the packaging and PyPi distribution of the unreleased 2.0.0 version, now that pelicanteam owns it on PyPi:

  • Rename master branch to main: WIP at #35.
  • Re-package plugin: WIP at #38.
  • Refactor and fix unittests. WIP at #39.
  • Setup CI/CD by the way of GitHub workflows.
  • Release v2.0.0 on PyPi.

Remove upper bound on `lxml` version

I'm trying to install a package that relies on this plugin, and it threw the error:

Encountered a bad command exit code!

Command: '.\\env\\env-2.9.2-tar.gz-pypi\\Scripts\\pip install -i https://pypi.org/pypi seafoam==2.9.2 --no-cache'

Exit code: 1

Stdout:

     ---------------------------------------- 3.5/3.5 MB 108.8 MB/s eta 0:00:00
Collecting pelican-jinja-filters>=2.1.0 (from seafoam==2.9.2)
  Downloading pelican_jinja_filters-2.1.1-py3-none-any.whl (11 kB)
Collecting semantic-version (from seafoam==2.9.2)
  Downloading semantic_version-2.10.0-py2.py3-none-any.whl (15 kB)
Collecting beautifulsoup4 (from seafoam==2.9.2)
  Downloading beautifulsoup4-4.12.3-py3-none-any.whl (147 kB)
     ---------------------------------------- 147.9/147.9 kB ? eta 0:00:00
Requirement already satisfied: Pillow<11,>=9.1 in d:\code\seafoam\env\env-2.9.2-tar.gz-pypi\lib\site-packages (from pelican-image-process>=2.1.1->seafoam==2.9.2) (10.2.0)
INFO: pip is looking at multiple versions of pelican-image-process to determine which version is compatible with other requirements. This could take a while.

Stderr:

ERROR: Could not find a version that satisfies the requirement lxml<5.0,>=4.6 (from pelican-image-process) (from versions: dev, 5.1bugfix, 5.1.0)
ERROR: No matching distribution found for lxml<5.0,>=4.6

Allowing lxml v5 shouldn't be an issue. The reason for the major version change seems to be that they dropped Python 2 support

BS with lxml fails (Python 3.4.3) on decoding element

/home/xael/python.venvs/newsletter/lib/python3.4/site-packages/bs4/__init__.py:166: UserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system ("lxml"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.

To get rid of this warning, change this:

 BeautifulSoup([your markup])

to this:

 BeautifulSoup([your markup], "lxml")

  markup_type=markup_type))
ERROR: Could not process newsletters/newsletter_09.md
  | AttributeError: 'NavigableString' object has no attribute 'decode'
  |___
  | Traceback (most recent call last):
  |   File "/home/xael/python.venvs/newsletter/lib/python3.4/site-packages/pelican/generators.py", line 514, in generate_context
  |     context_sender=self)
  |   File "/home/xael/python.venvs/newsletter/lib/python3.4/site-packages/pelican/readers.py", line 548, in read_file
  |     context=context)
  |   File "/home/xael/python.venvs/newsletter/lib/python3.4/site-packages/pelican/contents.py", line 149, in __init__
  |     signals.content_object_init.send(self)
  |   File "/home/xael/python.venvs/newsletter/lib/python3.4/site-packages/blinker/base.py", line 267, in send
  |     for receiver in self.receivers_for(sender)]
  |   File "/home/xael/python.venvs/newsletter/lib/python3.4/site-packages/blinker/base.py", line 267, in <listcomp>
  |     for receiver in self.receivers_for(sender)]
  |   File "/home/xael/ESPACE_KM/HESPUL/newsletter/pelican-plugins/image_process/image_process.py", line 172, in harvest_images
  |     instance._content = harvest_images_in_fragment(instance._content, instance.settings)
  |   File "/home/xael/ESPACE_KM/HESPUL/newsletter/pelican-plugins/image_process/image_process.py", line 236, in harvest_images_in_fragment
  |     new_fragment += element.decode()
  |   File "/home/xael/python.venvs/newsletter/lib/python3.4/site-packages/bs4/element.py", line 713, in __getattr__
  |     self.__class__.__name__, attr))
  | AttributeError: 'NavigableString' object has no attribute 'decode'

in this case, element is equal to \n.

Patch proposal :

diff --git a/image_process.py b/image_process.py
index 094bebe..96d70fc 100644
--- a/image_process.py
+++ b/image_process.py
@@ -233,9 +233,15 @@ def harvest_images_in_fragment(fragment, settings):
         if body:
             new_fragment = '';
             for element in body.children:
-                new_fragment += element.decode()
+                try:
+                    new_fragment += element.decode()
+                except AttributeError:
+                    new_fragment += element
         else:
-            new_fragment = soup.decode()
+            try:
+                new_fragment = soup.decode()
+            except AttributeError:
+                new_fragment += soup
     else:
         new_fragment = fragment

Animated GIF's become static

When I process animated GIF's through the plugin, they become static. It seems that Pillow should be able to resize animated images and maintain the animation, but I haven't yet figured out how to do that.

Stepping down as maintainer / pypi package admin

Hello everybody,

I would like to announce my intentions to step down as a maintainer of image_process and the related pypi repository.
The reason for this is that I decided to stop using all pelican plugins and the fact that I do not feel very comfortable with the code of this project.

I see that some other people are showing some real interest in this project. So I hereby would encourage you to step up as a potential maintainer of the project so that you can push some new releases. ๐Ÿ‘

@patrickfournier Can you let me know if you want to take over the pypi repository? Or maybe you can suggest somebody?

Remove stale branches

I spotted a couple of staled branches that I propose for deletion:

  • united-community-efforts => just a saving point used during the transition. Can be safely deleted.
  • release => Old readme fixes already merged.
  • pelican-plugins-pypi-move-warning => No longer necessary now that we migrated and reused the old repository.

You can delete branches at: https://github.com/pelican-plugins/image-process/branches

  • Oh, and while you're at it, maybe we can rename master to main (see #34)! :)

Output path being overwritten

Hi, I'm encountering an error when trying to use this plugin.

In my article.html file I am using this plugin to try to generate responsive images as such: <img class="book-cover image-process-crisp" alt="{{ article.title }}" src="../{{ article.cover }}" />

This works fine in terms of finding the correct file and generating the various srcset variants.

However, the URL's for these are being written as:
images/covers/derivatives/crisp/1x/lost-and-the-damned.jpg

Instead of the expected:

../images/covers/derivatives/crisp/1x/lost-and-the-damned.jpg

Which results in broken images across my site.

Is there any way for me to control the output path so that this works as expected?

responsive-image handling adds entries to srcset even if the source image is smaller

I have a set of images and some are large some are small.

My setup looks like this:

    'large': {
        'type': 'responsive-image',
        'srcset': [
            ('1000w', ["scale_out 1000 1 False"]),
            ('2000w', ["scale_out 2000 1 False"]),
            ('3000w', ["scale_out 3000 1 False"])
        ],
        'default': '1000w',
    },

I want 3 image sizes and no upscaling.

If the input image img_a.jpg is 3500px wide. I get a perfect srcset of:

<img 
   class="image-process-large" src="/large/1000w/img_a.jpg" 
   srcset="/large/1000w/img_a.jpg 1000w, /large/2000w/img_a.jpg 2000w, /large/3000w/img_a.jpg 3000w"/>

Where /large/1000w/img_a.jpg is 1000px wide, /large/2000w/img_a.jpg is 2000px wide and /large/3000w/img_a.jpg is 3000px wide.

But if I have an image img_b.jpg that is 1500px wide. I get the same srcset:

<img 
   class="image-process-large" src="/large/1000w/img_b.jpg" 
   srcset="/large/1000w/img_b.jpg 1000w, /large/2000w/img_b.jpg 2000w, /large/3000w/img_b.jpg 3000w"/>

Where /large/1000w/img_b.jpg is 1000px wide, /large/2000w/img_b.jpg is 1500px wide and /large/3000w/img_b.jpg is also 1500px wide.

It would be better if

  • /large/2000w/img_b.jpg and /large/3000w/img_b.jpg are not generated since they are not adding any information
  • the original image is used as a fallback

I propose that the generated tag sould look like this:

<img 
   class="image-process-large" src="/large/1000w/img_b.jpg" 
   srcset="/large/1000w/img_b.jpg 1000w, /img_b.jpg"/>

Bug in crop

in image_process.py line 65, left comes in front of top

return i.crop((int(t), int(l), int(r), int(b)))
should be instead
return i.crop((int(l), int(t), int(r), int(b)))

missing urlencode in `build_srcset()` to handle image filenames that contains whitespaces?

I've recently had a problem using the plugin with image files that have whitespaces in their name. This gives no problem on the src attribute as it has no separator. However for responsive-images, the srcset attribute uses whitespaces as separator between filename and the width/pixel-density descriptor, hence images filename with whitespaces in them breaking the property parsing of the browser.

I was able to fix it by modifying the build_srcset() function.
I added a urlencoding to the filename using urllib.parse.quote(), as such

for src in process["srcset"]:
        file_path = posixpath.join(path.base_url, src[0], urllib.parse.quote(path.filename))
        srcset.append("%s %s" % (file_path, src[0]))
        destination = os.path.join(path.base_path, src[0], path.filename)
        process_image((path.source, destination, src[1]), settings)

should this become a feature, and if yes it probably need to be done for the whole path including parents folder, and with a little more care than this quick edit.

External images are not filtered out

When image-process encounters non-local images, it try to open them and fail with this message:

$ pelican --verbose ./content
(...)
-> Writing /Users/kde/blog/output/2017/01/meta-package-manager-230/index.html
CRITICAL: [Errno 2] No such file or directory: '/Users/kde/blog/content/comics/universal_install_script.png'

Here I expect the plugin to detect the image resource as external, print a warning and skip the transformation altogether. Is that reasonable?

I'm using the latest version from the master branch.


Here is the detailed case, starting with this Markdown:

![XKCD #1654](https://imgs.xkcd.com/comics/universal_install_script.png){: .image-process-article-photo}

which properly renders to:

<img alt="XKCD #1654" class="image-process-article-photo" src="https://imgs.xkcd.com/comics/universal_install_script.png"/>

My pelicanconf.py look like this:

SITEURL = "http://localhost:8000"

PLUGINS = [
    (...)
    "pelican_image_process",
]

IMAGE_PROCESS = {
    "article-photo": {
        "type": "responsive-image",
        "sizes": """
            (min-width: 1200px) 800px,
            (min-width: 992px) 650px,
            (min-width: 768px) 718px,
            100vw
        """,
        "srcset": [
            # All keeps 4:3 aspect ratios.
            ("600w", ["scale_in 600 450 True"]),
            ("800w", ["scale_in 800 600 True"]),
            ("1600w", ["scale_in 1600 1200 True"]),
        ],
        # Default Plumage's central content column is 540px wide, so default to
        # 600w variant.
        "default": "600w",
    },
}
(...)

CRITICAL KeyError: 'IMAGE_PROCESS'

I'm in the process of setting up my first pelican blog on a Fedora Linux system. I've got pelican-4.8.0 with pelican-image-process-3.0.3. I've enabled pelican.plugins.image_process in the list of PLUGINS defined in pelicanconf.py, but have not yet added any other configuration options related to this plugin.

When I run 'make regenerate" (or "pelican --debug -r /home/netllama/stuff/llamaland/content -o /home/netllama/stuff/llamaland/output -s /home/netllama/stuff/llamaland/pelicanconf.py"), it run for many seconds but then fails with the following output:

...
           INFO     Copying /home/netllama/stuff/llamaland/themes/html5-dopetrope/static/images/all_flags/_somaliland.png to /home/netllama/stuff/llamaland/output/theme/images/all_flags/_somaliland.png            utils.py:332
           INFO     Copying /home/netllama/stuff/llamaland/themes/html5-dopetrope/static/images/all_flags/_south-ossetia.png to /home/netllama/stuff/llamaland/output/theme/images/all_flags/_south-ossetia.png      utils.py:332
           INFO     Copying /home/netllama/stuff/llamaland/themes/html5-dopetrope/static/images/all_flags/_united-nations.png to /home/netllama/stuff/llamaland/output/theme/images/all_flags/_united-nations.png    utils.py:332
           INFO     Copying /home/netllama/stuff/llamaland/themes/html5-dopetrope/static/images/all_flags/_unknown.png to /home/netllama/stuff/llamaland/output/theme/images/all_flags/_unknown.png                  utils.py:332
           INFO     Copying /home/netllama/stuff/llamaland/themes/html5-dopetrope/static/images/all_flags/_wales.png to /home/netllama/stuff/llamaland/output/theme/images/all_flags/_wales.png                      utils.py:332
           INFO     Copying /home/netllama/stuff/llamaland/themes/html5-dopetrope/static/js/config.js to /home/netllama/stuff/llamaland/output/theme/js/config.js                                                    utils.py:332
           INFO     Copying /home/netllama/stuff/llamaland/themes/html5-dopetrope/static/js/html5shiv.js to /home/netllama/stuff/llamaland/output/theme/js/html5shiv.js                                              utils.py:332
           INFO     Copying /home/netllama/stuff/llamaland/themes/html5-dopetrope/static/js/jquery.dropotron.js to /home/netllama/stuff/llamaland/output/theme/js/jquery.dropotron.js                                utils.py:332
           INFO     Copying /home/netllama/stuff/llamaland/themes/html5-dopetrope/static/js/jquery.min.js to /home/netllama/stuff/llamaland/output/theme/js/jquery.min.js                                            utils.py:332
           INFO     Copying /home/netllama/stuff/llamaland/themes/html5-dopetrope/static/js/skel-panels.min.js to /home/netllama/stuff/llamaland/output/theme/js/skel-panels.min.js                                  utils.py:332
           INFO     Copying /home/netllama/stuff/llamaland/themes/html5-dopetrope/static/js/skel.min.js to /home/netllama/stuff/llamaland/output/theme/js/skel.min.js                                                utils.py:332
           CRITICAL KeyError: 'IMAGE_PROCESS'                                                                                                                                                                     __init__.py:566
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Traceback (most recent call last) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/__init__.py:554 in โ”‚
โ”‚ main                                                                                             โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   551 โ”‚   โ”‚   โ”‚   if exc is not None:                                                            โ”‚
โ”‚   552 โ”‚   โ”‚   โ”‚   โ”‚   logger.critical(exc)                                                       โ”‚
โ”‚   553 โ”‚   โ”‚   elif args.autoreload:                                                              โ”‚
โ”‚ โฑ 554 โ”‚   โ”‚   โ”‚   autoreload(args)                                                               โ”‚
โ”‚   555 โ”‚   โ”‚   elif args.listen:                                                                  โ”‚
โ”‚   556 โ”‚   โ”‚   โ”‚   listen(settings.get('BIND'), settings.get('PORT'),                             โ”‚
โ”‚   557 โ”‚   โ”‚   โ”‚   โ”‚      settings.get("OUTPUT_PATH"))                                            โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/__init__.py:474 in โ”‚
โ”‚ autoreload                                                                                       โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   471 โ”‚   โ”‚   โ”‚   if any(modified.values()):                                                     โ”‚
โ”‚   472 โ”‚   โ”‚   โ”‚   โ”‚   console.print('\n-> Modified: {}. re-generating...'.format(                โ”‚
โ”‚   473 โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚     ', '.join(k for k, v in modified.items() if v)))             โ”‚
โ”‚ โฑ 474 โ”‚   โ”‚   โ”‚   โ”‚   pelican.run()                                                              โ”‚
โ”‚   475 โ”‚   โ”‚                                                                                      โ”‚
โ”‚   476 โ”‚   โ”‚   except KeyboardInterrupt:                                                          โ”‚
โ”‚   477 โ”‚   โ”‚   โ”‚   if excqueue is not None:                                                       โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/__init__.py:129 in โ”‚
โ”‚ run                                                                                              โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   126 โ”‚   โ”‚   โ”‚   if hasattr(p, 'generate_output'):                                              โ”‚
โ”‚   127 โ”‚   โ”‚   โ”‚   โ”‚   p.generate_output(writer)                                                  โ”‚
โ”‚   128 โ”‚   โ”‚                                                                                      โ”‚
โ”‚ โฑ 129 โ”‚   โ”‚   signals.finalized.send(self)                                                       โ”‚
โ”‚   130 โ”‚   โ”‚                                                                                      โ”‚
โ”‚   131 โ”‚   โ”‚   articles_generator = next(g for g in generators                                    โ”‚
โ”‚   132 โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚     if isinstance(g, ArticlesGenerator))                     โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/blinker/base.py:263 in     โ”‚
โ”‚ send                                                                                             โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   260 โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   '%s given' % len(sender))                                      โ”‚
โ”‚   261 โ”‚   โ”‚   else:                                                                              โ”‚
โ”‚   262 โ”‚   โ”‚   โ”‚   sender = sender[0]                                                             โ”‚
โ”‚ โฑ 263 โ”‚   โ”‚   return [(receiver, receiver(sender, **kwargs))                                     โ”‚
โ”‚   264 โ”‚   โ”‚   โ”‚   โ”‚   for receiver in self.receivers_for(sender)]                                โ”‚
โ”‚   265 โ”‚                                                                                          โ”‚
โ”‚   266 โ”‚   def has_receivers_for(self, sender):                                                   โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/blinker/base.py:263 in     โ”‚
โ”‚ <listcomp>                                                                                       โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   260 โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   โ”‚   '%s given' % len(sender))                                      โ”‚
โ”‚   261 โ”‚   โ”‚   else:                                                                              โ”‚
โ”‚   262 โ”‚   โ”‚   โ”‚   sender = sender[0]                                                             โ”‚
โ”‚ โฑ 263 โ”‚   โ”‚   return [(receiver, receiver(sender, **kwargs))                                     โ”‚
โ”‚   264 โ”‚   โ”‚   โ”‚   โ”‚   for receiver in self.receivers_for(sender)]                                โ”‚
โ”‚   265 โ”‚                                                                                          โ”‚
โ”‚   266 โ”‚   def has_receivers_for(self, sender):                                                   โ”‚
โ”‚                                                                                                  โ”‚
โ”‚ /home/netllama/stuff/llamaland_virtenv/lib64/python3.10/site-packages/pelican/plugins/image_proc โ”‚
โ”‚ ess/image_process.py:734 in dump_config                                                          โ”‚
โ”‚                                                                                                  โ”‚
โ”‚   731 def dump_config(pelican):                                                                  โ”‚
โ”‚   732 โ”‚   logger.debug(                                                                          โ”‚
โ”‚   733 โ”‚   โ”‚   "{} config:\n{}".format(                                                           โ”‚
โ”‚ โฑ 734 โ”‚   โ”‚   โ”‚   LOG_PREFIX, pprint.pformat(pelican.settings["IMAGE_PROCESS"])                  โ”‚
โ”‚   735 โ”‚   โ”‚   )                                                                                  โ”‚
โ”‚   736 โ”‚   )                                                                                      โ”‚
โ”‚   737                                                                                            โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
KeyError: 'IMAGE_PROCESS'

If I comment out the plugin, then the failure goes away. Its unclear to me exactly what is wrong, or how to resolve it.

Error message should have space

RuntimeError: Derivative article-feature definition not handled(must be list or dict)

There should be a space before the brackets.

(called from line 340 of image_process.py)

Global off switch

The plugin can take some time to run, and so for local development, i can be helpful to turn the plugin off. Previously, I'd do this by having differing lists of plugins in pelicanconf.py (for local development) and publishconf.py (for deployment), however with namespace plugins, I'm trying to move away from maintaining a list of plugins in either place.

So I would be helpful to have a "master" setting (like IMAGE_PROCESS_ACTIVE) that I could set to False and just turn it off for local development.

Hard coded first slash removel

I use gitlab pages to host my blog

my pages start from

user.gitlab.io/projectname

so i set SITEURL to "projectname" in the publishconf.py

and i have relative paths in my rst eg

.. image:: media/post/file.jpg

this brakes image_process as it looks for files called edia/post/file.jpg

i have a fix for this fishrockz@7a36587 but im not sure its the best way to do it.

would you like me to create a PR or is there a better way to confige image_process to avoid this?

Pillow 10 Deprecations

Several deprecation warnings of things that will be removed in Pillow 10:

pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path0-flip_horizontal-transform_params1]
pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path1-flip_horizontal-transform_params1]
  /home/runner/work/image-process/image-process/pelican/plugins/image_process/image_process.py:242: DeprecationWarning: FLIP_LEFT_RIGHT is deprecated and will be removed in Pillow 10 (2023-07-01). Use Transpose.FLIP_LEFT_RIGHT instead.
    "flip_horizontal": lambda i: i.transpose(Image.FLIP_LEFT_RIGHT),
pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path0-flip_vertical-transform_params2]
pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path1-flip_vertical-transform_params2]
  /home/runner/work/image-process/image-process/pelican/plugins/image_process/image_process.py:243: DeprecationWarning: FLIP_TOP_BOTTOM is deprecated and will be removed in Pillow 10 (2023-07-01). Use Transpose.FLIP_TOP_BOTTOM instead.
    "flip_vertical": lambda i: i.transpose(Image.FLIP_TOP_BOTTOM),
pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path0-resize-transform_params4]
pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path1-resize-transform_params4]
  /home/runner/work/image-process/image-process/pelican/plugins/image_process/image_process.py:172: DeprecationWarning: LANCZOS is deprecated and will be removed in Pillow 10 (2023-07-01). Use Resampling.LANCZOS instead.
    return i.resize((int(w), int(h)), Image.LANCZOS)
pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path0-rotate-transform_params5]
pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path1-rotate-transform_params5]
  /home/runner/work/image-process/image-process/pelican/plugins/image_process/image_process.py:228: DeprecationWarning: BICUBIC is deprecated and will be removed in Pillow 10 (2023-07-01). Use Resampling.BICUBIC instead.
    return i.rotate(int(degrees), Image.BICUBIC, True)
pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path0-scale_in-transform_params6]
pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path0-scale_out-transform_params7]
pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path1-scale_in-transform_params6]
pelican/plugins/image_process/test_image_process.py::test_all_transforms[image_path1-scale_out-transform_params7]
  /home/runner/work/image-process/image-process/pelican/plugins/image_process/image_process.py:218: DeprecationWarning: LANCZOS is deprecated and will be removed in Pillow 10 (2023-07-01). Use Resampling.LANCZOS instead.
    return i.resize((int(scale * iw), int(scale * ih)), Image.LANCZOS)
pelican/plugins/image_process/test_image_process.py::test_exiftool_process_is_started_only_when_necessary[True]
pelican/plugins/image_process/test_image_process.py::test_exiftool_process_is_started_only_when_necessary[False]
  /home/runner/work/image-process/image-process/pelican/plugins/image_process/image_process.py:418: DeprecationWarning: The 'warn' method is deprecated, use 'warning' instead
    log.warn(
-- Docs: https://docs.pytest.org/en/stable/warnings.html

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.